> ## Documentation Index
> Fetch the complete documentation index at: https://docs.authsignal.com/llms.txt
> Use this file to discover all available pages before exploring further.

# User actions

> Learn how to track, query, and manage user actions in Authsignal

## What are user actions?

Actions represent security-relevant events in your application and serve as the foundation for implementing:

* **Multi-factor authentication (MFA)** - Challenge users after primary authentication
* **Step-up authentication** - Require additional verification for sensitive operations
* **Adaptive authentication** - Apply rules based on risk factors and context
* **Passwordless authentication** - Use Authsignal as the primary authentication method

Every action has an associated **state** that determines how your application should respond to the user's request.

## Action operations

### Track action

The primary method for recording user activities and initiating authentication challenges. This is the core operation you'll use throughout your application.

<Tabs>
  <Tab title="Custom UI">
    <CodeGroup>
      ```ts Node.js theme={null}
      // Track an action on your backend
      const result = await authsignal.track({
        userId: "0272c312-e181-4cad-a494-43647b503a0a",
        action: "withdraw-funds",
        attributes: {
          deviceId: "555c17e1-3837-4f13-81bb-131e5597e168",
          ipAddress: "203.0.113.42",
        },
      });

      // Handle different action outcomes
      if (result.state === "CHALLENGE_REQUIRED") {
        // User needs to complete a challenge
        return {
          token: result.token, 
        };
      } else if (result.state === "ALLOW") {
        // Proceed with the action
        return { success: true };
      } else if (result.state === "REVIEW") {
        // Action requires manual review
        return { 
          status: "under_review",
          message: "Your request is being reviewed"
        };
      } else if (result.state === "BLOCK") {
        // Action is blocked
        return { 
          error: "This action cannot be completed for security reasons"
        };
      }
      ```

      ```csharp C# theme={null}
      // Track an action on your backend
      var request = new TrackRequest(
          UserId: "0272c312-e181-4cad-a494-43647b503a0a",
          Action: "withdraw-funds",
          Attributes: new TrackAttributes(
              DeviceId: "555c17e1-3837-4f13-81bb-131e5597e168",
              IpAddress: "203.0.113.42"
          )
      );

      var result = await authsignal.Track(request);

      // Handle different action outcomes
      if (result.State == "CHALLENGE_REQUIRED") 
      {
          // User needs to complete a challenge
          return new {
              Token = result.Token
          };
      }
      else if (result.State == "ALLOW") 
      {
          // Proceed with the action
          return new { Success = true };
      }
      else if (result.State == "REVIEW") 
      {
          // Action requires manual review
          return new { 
              Status = "under_review",
              Message = "Your request is being reviewed"
          };
      }
      else if (result.State == "BLOCK") 
      {
          // Action is blocked
          return new { 
              Error = "This action cannot be completed for security reasons"
          };
      }
      ```

      ```java Java theme={null}
      // Track an action on your backend
      TrackRequest request = new TrackRequest();
      request.userId = "0272c312-e181-4cad-a494-43647b503a0a";
      request.action = "withdraw-funds";
      request.attributes = new TrackAttributes();
      request.attributes.deviceId = "555c17e1-3837-4f13-81bb-131e5597e168";
      request.attributes.ipAddress = "203.0.113.42";

      TrackResponse result = authsignal.track(request).get();

      // Handle different action outcomes
      if ("CHALLENGE_REQUIRED".equals(result.state)) {
          // User needs to complete a challenge
          Map<String, Object> response = new HashMap<>();
          response.put("token", result.token);
          return response;
      } else if ("ALLOW".equals(result.state)) {
          // Proceed with the action
          return Map.of("success", true);
      } else if ("REVIEW".equals(result.state)) {
          // Action requires manual review
          return Map.of(
              "status", "under_review",
              "message", "Your request is being reviewed"
          );
      } else if ("BLOCK".equals(result.state)) {
          // Action is blocked
          return Map.of(
              "error", "This action cannot be completed for security reasons"
          );
      }
      ```

      ```ruby Ruby theme={null}
      # Track an action on your backend
      result = Authsignal.track({
        user_id: "0272c312-e181-4cad-a494-43647b503a0a",
        action: "withdraw-funds",
        attributes: {
          device_id: "555c17e1-3837-4f13-81bb-131e5597e168",
          ip_address: "203.0.113.42",
        }
      })

      # Handle different action outcomes
      case result[:state]
      when "CHALLENGE_REQUIRED"
        # User needs to complete a challenge
        {
          token: result[:token]
        }
      when "ALLOW"
        # Proceed with the action
        { success: true }
      when "REVIEW"
        # Action requires manual review
        {
          status: "under_review",
          message: "Your request is being reviewed"
        }
      when "BLOCK"
        # Action is blocked
        {
          error: "This action cannot be completed for security reasons"
        }
      end
      ```

      ```python Python theme={null}
      # Track an action on your backend
      result = authsignal.track(
          user_id="0272c312-e181-4cad-a494-43647b503a0a",
          action="withdraw-funds",
          attributes={
              "deviceId": "555c17e1-3837-4f13-81bb-131e5597e168",
              "ipAddress": "203.0.113.42"
          }
      )

      # Handle different action outcomes
      if result["state"] == "CHALLENGE_REQUIRED":
          # User needs to complete a challenge
          return {
              "token": result["token"]
          }
      elif result["state"] == "ALLOW":
          # Proceed with the action
          return {"success": True}
      elif result["state"] == "REVIEW":
          # Action requires manual review
          return {
              "status": "under_review",
              "message": "Your request is being reviewed"
          }
      elif result["state"] == "BLOCK":
          # Action is blocked
          return {
              "error": "This action cannot be completed for security reasons"
          }
      ```

      ```php PHP theme={null}
      // Track an action on your backend
      $result = Authsignal::track([
          'userId' => "0272c312-e181-4cad-a494-43647b503a0a",
          'action' => "withdraw-funds",
          'attributes' => [
              'deviceId' => "555c17e1-3837-4f13-81bb-131e5597e168",
              'ipAddress' => "203.0.113.42"
          ]
      ]);

      // Handle different action outcomes
      switch ($result["state"]) {
          case "CHALLENGE_REQUIRED":
              // User needs to complete a challenge
              return [
                  'token' => $result["token"]
              ];
          case "ALLOW":
              // Proceed with the action
              return ['success' => true];
          case "REVIEW":
              // Action requires manual review
              return [
                  'status' => "under_review",
                  'message' => "Your request is being reviewed"
              ];
          case "BLOCK":
              // Action is blocked
              return [
                  'error' => "This action cannot be completed for security reasons"
              ];
      }
      ```

      ```go Go theme={null}
      // Track an action on your backend
      result, err := client.Track(
          TrackRequest{
              UserId: "0272c312-e181-4cad-a494-43647b503a0a",
              Action: "withdraw-funds",
              Attributes: &TrackAttributes{
                  DeviceId:  "555c17e1-3837-4f13-81bb-131e5597e168",
                  IpAddress: "203.0.113.42",
              },
          },
      )
      if err != nil {
          return err
      }

      // Handle different action outcomes
      switch result.State {
      case "CHALLENGE_REQUIRED":
          // User needs to complete a challenge
          return map[string]interface{}{
              "token": result.Token,
          }
      case "ALLOW":
          // Proceed with the action
          return map[string]interface{}{"success": true}
      case "REVIEW":
          // Action requires manual review
          return map[string]interface{}{
              "status":  "under_review",
              "message": "Your request is being reviewed",
          }
      case "BLOCK":
          // Action is blocked
          return map[string]interface{}{
              "error": "This action cannot be completed for security reasons",
          }
      }
      ```
    </CodeGroup>
  </Tab>

  <Tab title="Pre-built UI">
    <CodeGroup>
      ```ts Node.js theme={null}
      // Track an action on your backend
      const result = await authsignal.track({
        userId: "0272c312-e181-4cad-a494-43647b503a0a",
        action: "withdraw-funds",
        attributes: {
          redirectUrl: "https://yourapp.com/callback",
          deviceId: "deviceId",
          ipAddress: "ipAddress",
        },
      });

      // Handle different action outcomes
      if (result.state === "CHALLENGE_REQUIRED") {
        // User needs to complete a challenge
        return {
          url: result.url, 
        };
      } else if (result.state === "ALLOW") {
        // Proceed with the action
        return { success: true };
      } else if (result.state === "REVIEW") {
        // Action requires manual review
        return { 
          status: "under_review",
          message: "Your request is being reviewed"
        };
      } else if (result.state === "BLOCK") {
        // Action is blocked
        return { 
          error: "This action cannot be completed for security reasons"
        };
      }
      ```

      ```csharp C# theme={null}
      // Track an action on your backend
      var request = new TrackRequest(
          UserId: "0272c312-e181-4cad-a494-43647b503a0a",
          Action: "withdraw-funds",
          Attributes: new TrackAttributes(
              RedirectUrl: "https://yourapp.com/callback",
              DeviceId: "555c17e1-3837-4f13-81bb-131e5597e168",
              IpAddress: "203.0.113.42"
          )
      );

      var result = await authsignal.Track(request);

      // Handle different action outcomes
      if (result.State == "CHALLENGE_REQUIRED") 
      {
          // User needs to complete a challenge
          return new {
              Url = result.Url
          };
      }
      else if (result.State == "ALLOW") 
      {
          // Proceed with the action
          return new { Success = true };
      }
      else if (result.State == "REVIEW") 
      {
          // Action requires manual review
          return new { 
              Status = "under_review",
              Message = "Your request is being reviewed"
          };
      }
      else if (result.State == "BLOCK") 
      {
          // Action is blocked
          return new { 
              Error = "This action cannot be completed for security reasons"
          };
      }
      ```

      ```java Java theme={null}
      // Track an action on your backend
      TrackRequest request = new TrackRequest();
      request.userId = "0272c312-e181-4cad-a494-43647b503a0a";
      request.action = "withdraw-funds";
      request.attributes = new TrackAttributes();
      request.attributes.redirectUrl = "https://yourapp.com/callback";
      request.attributes.deviceId = "555c17e1-3837-4f13-81bb-131e5597e168";
      request.attributes.ipAddress = "203.0.113.42";

      TrackResponse result = authsignal.track(request).get();

      // Handle different action outcomes
      if ("CHALLENGE_REQUIRED".equals(result.state)) {
          // User needs to complete a challenge
          Map<String, Object> response = new HashMap<>();
          response.put("url", result.url);
          return response;
      } else if ("ALLOW".equals(result.state)) {
          // Proceed with the action
          return Map.of("success", true);
      } else if ("REVIEW".equals(result.state)) {
          // Action requires manual review
          return Map.of(
              "status", "under_review",
              "message", "Your request is being reviewed"
          );
      } else if ("BLOCK".equals(result.state)) {
          // Action is blocked
          return Map.of(
              "error", "This action cannot be completed for security reasons"
          );
      }
      ```

      ```ruby Ruby theme={null}
      # Track an action on your backend
      result = Authsignal.track({
        user_id: "0272c312-e181-4cad-a494-43647b503a0a",
        action: "withdraw-funds",
        attributes: {
          redirect_url: "https://yourapp.com/callback",
          device_id: "555c17e1-3837-4f13-81bb-131e5597e168",
          ip_address: "203.0.113.42",
        }
      })

      # Handle different action outcomes
      case result[:state]
      when "CHALLENGE_REQUIRED"
        # User needs to complete a challenge
        {
          url: result[:url]
        }
      when "ALLOW"
        # Proceed with the action
        { success: true }
      when "REVIEW"
        # Action requires manual review
        {
          status: "under_review",
          message: "Your request is being reviewed"
        }
      when "BLOCK"
        # Action is blocked
        {
          error: "This action cannot be completed for security reasons"
        }
      end
      ```

      ```python Python theme={null}
      # Track an action on your backend
      result = authsignal.track(
          user_id="0272c312-e181-4cad-a494-43647b503a0a",
          action="withdraw-funds",
          attributes={
              "redirectUrl": "https://yourapp.com/callback",
              "deviceId": "555c17e1-3837-4f13-81bb-131e5597e168",
              "ipAddress": "203.0.113.42"
          }
      )

      # Handle different action outcomes
      if result["state"] == "CHALLENGE_REQUIRED":
          # User needs to complete a challenge
          return {
              "url": result["url"]
          }
      elif result["state"] == "ALLOW":
          # Proceed with the action
          return {"success": True}
      elif result["state"] == "REVIEW":
          # Action requires manual review
          return {
              "status": "under_review",
              "message": "Your request is being reviewed"
          }
      elif result["state"] == "BLOCK":
          # Action is blocked
          return {
              "error": "This action cannot be completed for security reasons"
          }
      ```

      ```php PHP theme={null}
      // Track an action on your backend
      $result = Authsignal::track([
          'userId' => "0272c312-e181-4cad-a494-43647b503a0a",
          'action' => "withdraw-funds",
          'attributes' => [
              'redirectUrl' => "https://yourapp.com/callback",
              'deviceId' => "555c17e1-3837-4f13-81bb-131e5597e168",
              'ipAddress' => "203.0.113.42"
          ]
      ]);

      // Handle different action outcomes
      switch ($result["state"]) {
          case "CHALLENGE_REQUIRED":
              // User needs to complete a challenge
              return [
                  'url' => $result["url"]
              ];
          case "ALLOW":
              // Proceed with the action
              return ['success' => true];
          case "REVIEW":
              // Action requires manual review
              return [
                  'status' => "under_review",
                  'message' => "Your request is being reviewed"
              ];
          case "BLOCK":
              // Action is blocked
              return [
                  'error' => "This action cannot be completed for security reasons"
              ];
      }
      ```

      ```go Go theme={null}
      // Track an action on your backend
      result, err := client.Track(
          TrackRequest{
              UserId: "0272c312-e181-4cad-a494-43647b503a0a",
              Action: "withdraw-funds",
              Attributes: &TrackAttributes{
                  RedirectUrl: "https://yourapp.com/callback",
                  DeviceId:    "555c17e1-3837-4f13-81bb-131e5597e168",
                  IpAddress:   "203.0.113.42",
              },
          },
      )
      if err != nil {
          return err
      }

      // Handle different action outcomes
      switch result.State {
      case "CHALLENGE_REQUIRED":
          // User needs to complete a challenge
          return map[string]interface{}{
              "url": result.Url,
          }
      case "ALLOW":
          // Proceed with the action
          return map[string]interface{}{"success": true}
      case "REVIEW":
          // Action requires manual review
          return map[string]interface{}{
              "status":  "under_review",
              "message": "Your request is being reviewed",
          }
      case "BLOCK":
          // Action is blocked
          return map[string]interface{}{
              "error": "This action cannot be completed for security reasons",
          }
      }
      ```
    </CodeGroup>
  </Tab>
</Tabs>

When you track an action, you provide:

* **User ID** - Unique identifier for the user
* **Action code** - What the user is doing (e.g., "signIn", "withdrawFunds")
* **Attributes** - Contextual information for risk assessment

### Get action

Retrieve detailed information about a previously tracked action using its unique identifiers.

<CodeGroup>
  ```ts Node.js theme={null}
  const request = {
    userId: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833",
    action: "signIn",
    idempotencyKey: "83d07321-9bba-4f08-a871-02e2af813b72",
  };

  const response = await authsignal.getAction(request);
  ```

  ```csharp C# theme={null}
  var request = new GetActionRequest(
      UserId: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833",
      Action: "signIn",
      IdempotencyKey: "83d07321-9bba-4f08-a871-02e2af813b72"
  );

  var response = await authsignal.GetAction(request);
  ```

  ```java Java theme={null}
  GetActionRequest request = new GetActionRequest();
  request.userId = "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833";
  request.action = "signIn";
  request.idempotencyKey = "83d07321-9bba-4f08-a871-02e2af813b72";

  GetActionResponse response = authsignal.getAction(request).get();
  ```

  ```ruby Ruby theme={null}
  response = Authsignal.get_action(
      user_id: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833",
      action: "signIn",
      idempotency_key: "83d07321-9bba-4f08-a871-02e2af813b72")
  ```

  ```python Python theme={null}
  response = authsignal.get_action(
      user_id="dc58c6dc-a1fd-4a4f-8e2f-846636dd4833",
      action="signIn",
      idempotency_key="83d07321-9bba-4f08-a871-02e2af813b72"
  )
  ```

  ```PHP PHP theme={null}
  $response = Authsignal::getAction([
      'userId' => "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833",
      'action' => "signIn",
      'idempotencyKey' => "83d07321-9bba-4f08-a871-02e2af813b72"
  ]);
  ```

  ```go Go theme={null}
  response, err := client.GetAction(
      GetActionRequest{
          UserId:         "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833",
          Action:         "signIn",
          IdempotencyKey: "83d07321-9bba-4f08-a871-02e2af813b72",
      },
  )
  ```
</CodeGroup>

### Query actions

Retrieve a list of actions for a specific user to view their authentication history.

<CodeGroup>
  ```ts Node.js theme={null}
  const request = {
    userId: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833",
    // Optional filters
    codes: "signIn,withdrawFunds", // Comma-separated list of action codes
    fromDate: "2024-01-01T00:00:00Z", // ISO 8601 format
  };

  const actions = await authsignal.queryActions(request);
  ```

  ```csharp C# theme={null}
  var request = new QueryActionsRequest(
      UserId: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833",
      Codes: "signIn,withdrawFunds", // Comma-separated list of action codes
      FromDate: "2024-01-01T00:00:00Z" // ISO 8601 format
  );

  var actions = await authsignal.QueryActions(request);
  ```

  ```java Java theme={null}
  QueryActionsRequest request = new QueryActionsRequest();
  request.userId = "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833";
  request.codes = "signIn,withdrawFunds"; // Comma-separated list of action codes
  request.fromDate = "2024-01-01T00:00:00Z"; // ISO 8601 format

  QueryActionsResponse actions = authsignal.queryActions(request).get();
  ```

  ```ruby Ruby theme={null}
  actions = Authsignal.query_actions(
    user_id: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833",
    codes: "signIn,withdrawFunds", # Comma-separated list of action codes
    from_date: "2024-01-01T00:00:00Z" # ISO 8601 format
  )
  ```

  ```python Python theme={null}
  actions = authsignal.query_actions(
      user_id="dc58c6dc-a1fd-4a4f-8e2f-846636dd4833",
      codes="signIn,withdrawFunds",  # Comma-separated list of action codes
      from_date="2024-01-01T00:00:00Z"  # ISO 8601 format
  )
  ```

  ```php PHP theme={null}
  $actions = Authsignal::queryActions([
      'userId' => "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833",
      'codes' => "signIn,withdrawFunds", // Comma-separated list of action codes
      'fromDate' => "2024-01-01T00:00:00Z" // ISO 8601 format
  ]);
  ```

  ```go Go theme={null}
  actions, err := client.QueryActions(
      QueryActionsRequest{
          UserId: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833",
          Codes: "signIn,withdrawFunds", // Comma-separated list of action codes
          FromDate: "2024-01-01T00:00:00Z", // ISO 8601 format
      },
  )
  ```
</CodeGroup>

### Update action

Manually modify the state of a previously tracked action. This is useful for administrative actions or custom workflows.

<CodeGroup>
  ```ts Node.js theme={null}
  const request = {
    userId: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833",
    action: "signIn",
    idempotencyKey: "83d07321-9bba-4f08-a871-02e2af813b72",
    attributes: {
      state: "REVIEW_REQUIRED",
    },
  };

  const updatedAttributes = await authsignal.updateAction(request);
  ```

  ```csharp C# theme={null}
  var request = new UpdateActionRequest(
      UserId: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833",
      Action: "signIn",
      IdempotencyKey: "83d07321-9bba-4f08-a871-02e2af813b72",
      Attributes: new ActionAttributes(
          state: UserActionState.REVIEW_REQUIRED
      )
  );

  var updatedAttributes = await authsignal.UpdateAction(request);
  ```

  ```java Java theme={null}
  UpdateActionRequest request = new UpdateActionRequest();
  request.userId = "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833";
  request.action = "signIn";
  request.idempotencyKey = "83d07321-9bba-4f08-a871-02e2af813b72";
  request.attributes = new ActionAttributes();
  request.attributes.state = UserActionState.REVIEW_REQUIRED;

  ActionAttributes updatedAttributes = authsignal.updateAction(request).get();
  ```

  ```ruby Ruby theme={null}
  updatedAttributes = Authsignal.update_action(
      user_id: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833",
      action: "signIn",
      idempotency_key: "83d07321-9bba-4f08-a871-02e2af813b72",
      attributes: {
          state: "REVIEW_REQUIRED"
      })
  ```

  ```python Python theme={null}
  updatedAttributes = authsignal.update_action(
      user_id="dc58c6dc-a1fd-4a4f-8e2f-846636dd4833",
      action="signIn",
      idempotency_key="83d07321-9bba-4f08-a871-02e2af813b72",
      attributes={
          "state": "REVIEW_REQUIRED"
      }
  )
  ```

  ```PHP PHP theme={null}
  $attributes = array(
      "state" => "REVIEW_REQUIRED",
  );

  $updatedAttributes = Authsignal::updateAction(
      userId: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833",
      action: "signIn",
      idempotencyKey: "83d07321-9bba-4f08-a871-02e2af813b72",
      attributes: $attributes);
  ```

  ```go Go theme={null}
  updatedAttributes, err := client.UpdateAction(
      UpdateActionRequest{
          UserId: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833",
          Action: "signIn",
          IdempotencyKey: "83d07321-9bba-4f08-a871-02e2af813b72",
          Attributes: &ActionAttributes{
              State: "REVIEW_REQUIRED",
          },
      },
  )


  ```
</CodeGroup>

## Action states

Every action results in one of these states that determine how your application should respond:

| State                | Description                            | Recommended Action                   |
| -------------------- | -------------------------------------- | ------------------------------------ |
| `ALLOW`              | User is trusted, no challenge required | Proceed with the requested operation |
| `CHALLENGE_REQUIRED` | User must complete authentication      | Present authentication challenge     |
| `REVIEW`             | Action requires manual review          | Queue for administrative review      |
| `BLOCK`              | Action is blocked for security reasons | Deny the requested operation         |

### State transitions

Actions can transition between states based on user interactions and administrative actions:

```mermaid theme={null}
flowchart TD
    A[Track action] --> B{Rules engine}
    B --> C[ALLOW]
    B --> D[CHALLENGE_REQUIRED]
    B --> E[REVIEW]
    B --> F[BLOCK]
    
    D --> G[User completes challenge]
    G --> H[CHALLENGE_SUCCEEDED]
    G --> I[CHALLENGE_FAILED]
    
    E --> J[Admin review]
    J --> K[REVIEW_SUCCEEDED]
    J --> L[REVIEW_FAILED]
```

## Action attributes

When tracking actions, you can provide contextual information that helps with risk assessment and rule evaluation:

### Standard attributes

| Attribute            | Type    | Description                                               |
| -------------------- | ------- | --------------------------------------------------------- |
| `email`              | string  | User's email address                                      |
| `phoneNumber`        | string  | User's phone number in E.164 format                       |
| `deviceId`           | string  | Unique device identifier (from Authsignal Web SDK cookie) |
| `userAgent`          | string  | Browser user agent string                                 |
| `ipAddress`          | string  | User's IP address                                         |
| `redirectUrl`        | string  | URL for redirect after pre-built UI completion            |
| `redirectToSettings` | boolean | Show settings page after challenge completion             |

### Custom attributes

Use the `custom` field to pass business-specific data for use in rules:

```ts theme={null}
const response = await authsignal.track({
  userId: "user123",
  action: "transferFunds",
  attributes: {
    deviceId: "device-abc",
    ipAddress: "203.0.113.1",
    custom: {
      transferAmount: 5000,
      recipientCountry: "US",
      accountTier: "premium"
    }
  }
});
```

This custom data can then be used in [Authsignal rules](/actions-rules/rules/custom-data-points) to make intelligent authentication decisions.

## Action lifecycle

Understanding the complete action lifecycle helps you implement robust authentication flows:

### 1. Action creation

Actions are created when you call `track()`. Each action gets:

* Unique identifiers (`userId`, `action`, `idempotencyKey`)
* Initial state based on rules evaluation
* Contextual metadata (IP, user agent, custom data)

### 2. Rule evaluation

When an action is tracked, Authsignal's rules engine evaluates:

* User enrollment status
* Configured rules and conditions
* Risk factors and context
* Custom business logic

### 3. Challenge flow (if required)

For `CHALLENGE_REQUIRED` actions:

* Generate short-lived token or URL
* User completes authentication via pre-built UI or Client SDKs
* Action state updates to `CHALLENGE_SUCCEEDED` or `CHALLENGE_FAILED`

### 4. Validation

Your application validates the challenge result and proceeds based on the final state.

## Integration patterns

### Just-in-time authentication

Track actions at the point where authentication is needed:

```ts theme={null}
async function handleSensitiveOperation(userId: string) {
  // Track the action first
  const result = await authsignal.track({
    userId,
    action: "accessSensitiveData",
    attributes: {
      ipAddress: request.ip,
      userAgent: request.headers.userAgent
    }
  });

  // Handle based on result
  switch (result.state) {
    case "ALLOW":
      return performSensitiveOperation();
    case "CHALLENGE_REQUIRED":
      return { challengeUrl: result.url };
    case "BLOCK":
      throw new Error("Access denied");
    case "REVIEW":
      return { status: "pending_review" };
  }
}
```

### Pre-authentication

Track actions before performing operations to determine if additional security is needed:

```ts theme={null}
async function initiatePayment(userId: string, amount: number) {
  // Check if payment requires additional authentication
  const result = await authsignal.track({
    userId,
    action: "makePayment",
    attributes: {
      custom: { paymentAmount: amount }
    }
  });

  if (result.state === "CHALLENGE_REQUIRED") {
    return { requiresAuth: true, challengeUrl: result.url };
  }

  // Proceed with payment
  return processPayment(amount);
}
```

### Idempotency

Actions are automatically deduplicated using idempotency keys. Multiple calls with the same user ID, action, and idempotency key will return the same result.

## Next steps

* [Set up your first action](/actions-rules/actions/implementing-mfa)
* [Create rules for adaptive authentication](/actions-rules/rules/adaptive-mfa)
* [Implement custom data points](/actions-rules/rules/custom-data-points)
