> ## 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.

# Getting started with actions

> Learn how Authsignal actions enable risk-based authentication and how to implement them in your app.

Authsignal actions are the building blocks that let you create contextual, risk-based authentication flows.

Actions represent specific user activities or events in your application that might require authentication. These can range from routine operations like sign-in to high-risk activities like withdrawing funds, changing account settings, or making large purchases.

## Creating an action

To create an action, navigate to the [actions page](https://portal.authsignal.com/actions) and click the **Create a new action** button.

<Frame>
  <img src="https://mintcdn.com/authsignal-23/U9XmxdPe4AWAeEuC/images/docs/actions-rules/creating-action.png?fit=max&auto=format&n=U9XmxdPe4AWAeEuC&q=85&s=8f69e7235d23e1578846659e4cab1f81" alt="Creating actions in the Authsignal dashboard" width="2952" height="1534" data-path="images/docs/actions-rules/creating-action.png" />
</Frame>

## Core components of an action

### 1. Action outcomes

Every action in Authsignal results in one of four possible outcomes that determine how to handle the user's request:

* **ALLOW:** Let the action proceed without additional authentication
* **CHALLENGE:** Require the user to complete an authentication challenge
* **REVIEW:** Place the action in a queue for manual review
* **BLOCK:** Prevent the action from proceeding entirely

<Frame>
  <img src="https://mintcdn.com/authsignal-23/U9XmxdPe4AWAeEuC/images/docs/actions-rules/action-outcomes.png?fit=max&auto=format&n=U9XmxdPe4AWAeEuC&q=85&s=8b2000a7047839cf146677bce40dd6d8" alt="Action outcomes" width="2994" height="1584" data-path="images/docs/actions-rules/action-outcomes.png" />
</Frame>

Each action has a configurable default outcome that determines what happens when no rules are triggered. However, when you create rules for an action, those rules can override the default outcome.

### 2. Rules

The conditional logic that determines which outcome to apply based on risk factors and context. Rules can override the action's default outcome.
When you track an action, you provide the context needed for evaluation:

```js theme={null}
await authsignal.track({
  userId: "0272c312-e181-4cad-a494-43647b503a0a", // Unique identifier for the user
  action: "withdraw-funds", // The action code (what the user is doing)
  attributes: {
    // Contextual information for rule evaluation
    deviceId: "555c17e1-3837-4f13-81bb-131e5597e168",
    ipAddress: "203.0.113.42",
    userAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
  },
});
```

## Setting up your action

### 1. Tracking an action

In your app's backend, use an [Authsignal Server SDK](/sdks/server) to track an action which represents what your user is doing (e.g. `withdraw-funds`).

This step will return a token which can be passed to a client SDK to perform a challenge for that user.

<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>

### 2. Challenging the user

Challenging the user performing an action.

<Tabs>
  <Tab title="Custom UI">
    In your frontend, call `setToken` with the client token obtained, then use the relevant SDK methods to progress the user through a challenge.

    ```js theme={null}
    // Set the token from the track result
    authsignal.setToken(token);

    // Show the appropriate challenge based on the user's enrolled methods
    const result = await authsignal.passkey.signIn({
      action: "withdraw-funds",
    });

    // Send the result token back to your server for validation
    if (result.token) {
      await validateChallenge(result.token);
    }
    ```
  </Tab>

  <Tab title="Pre-built UI">
    In your frontend, pass the url from the `track` call to the [Authsignal Web SDK](/sdks/client/web/setup) to launch an enrollment or re-authentication flow.

    ```js theme={null}
    // Launch the Pre-built UI
    authsignal.launch({
      url: challengeUrl,
      mode: "popup", // or "redirect"
    });
    ```
  </Tab>
</Tabs>

### 3. Validating a challenge

After the user completes the challenge, you'll receive a `token` that you can validate on your backend to verify the authentication result.

For pre-built UI, this `token` is appended to your redirect URL as a query parameter, while for custom UI implementation, you'll get the `token` directly from the challenge completion result.

<Tabs>
  <Tab title="Custom UI">
    Pass the token obtained from the challenge result to your backend and validate it server-side to complete authentication.

    <CodeGroup>
      ```ts Node.js theme={null}
      const request = {
        token: "eyJhbGciOiJ...", // Token from challenge completion
      };

      const response = await authsignal.validateChallenge(request);

      if (response.state === "CHALLENGE_SUCCEEDED") {
        // The user completed the challenge successfully
        // Proceed with authenticated action or create authenticated session
        return { success: true, userId: response.userId };
      } else {
        // The user did not complete the challenge successfully
        return { error: "Challenge validation failed" };
      }
      ```

      ```csharp C# theme={null}
      var request = new ValidateChallengeRequest(
          Token: "eyJhbGciOiJ..." // Token from challenge completion
      );

      var response = await authsignal.ValidateChallenge(request);

      if (response.State == "CHALLENGE_SUCCEEDED") 
      {
          // The user completed the challenge successfully
          // Proceed with authenticated action or create authenticated session
          return new { Success = true, UserId = response.UserId };
      }
      else 
      {
          // The user did not complete the challenge successfully
          return new { Error = "Challenge validation failed" };
      }
      ```

      ```java Java theme={null}
      ValidateChallengeRequest request = new ValidateChallengeRequest();
      request.token = "eyJhbGciOiJ..."; // Token from challenge completion

      ValidateChallengeResponse response = authsignal.validateChallenge(request).get();

      if ("CHALLENGE_SUCCEEDED".equals(response.state)) {
          // The user completed the challenge successfully
          // Proceed with authenticated action or create authenticated session
          return Map.of("success", true, "userId", response.userId);
      } else {
          // The user did not complete the challenge successfully
          return Map.of("error", "Challenge validation failed");
      }
      ```

      ```ruby Ruby theme={null}
      response = Authsignal.validate_challenge({
        token: "eyJhbGciOiJ..." # Token from challenge completion
      })

      if response[:state] == "CHALLENGE_SUCCEEDED"
        # The user completed the challenge successfully
        # Proceed with authenticated action or create authenticated session
        { success: true, user_id: response[:user_id] }
      else
        # The user did not complete the challenge successfully
        { error: "Challenge validation failed" }
      end
      ```

      ```python Python theme={null}
      response = authsignal.validate_challenge(
          token="eyJhbGciOiJ..."  # Token from challenge completion
      )

      if response["state"] == "CHALLENGE_SUCCEEDED":
          # The user completed the challenge successfully
          # Proceed with authenticated action or create authenticated session
          return {"success": True, "userId": response["userId"]}
      else:
          # The user did not complete the challenge successfully
          return {"error": "Challenge validation failed"}
      ```

      ```php PHP theme={null}
      $response = Authsignal::validateChallenge([
          'token' => "eyJhbGciOiJ..." // Token from challenge completion
      ]);

      if ($response["state"] === "CHALLENGE_SUCCEEDED") {
          // The user completed the challenge successfully
          // Proceed with authenticated action or create authenticated session
          return [
              'success' => true, 
              'userId' => $response["userId"]
          ];
      } else {
          // The user did not complete the challenge successfully
          return ['error' => "Challenge validation failed"];
      }
      ```

      ```go Go theme={null}
      response, err := client.ValidateChallenge(
          ValidateChallengeRequest{
              Token: "eyJhbGciOiJ...", // Token from challenge completion
          },
      )
      if err != nil {
          return err
      }

      if response.State == "CHALLENGE_SUCCEEDED" {
          // The user completed the challenge successfully
          // Proceed with authenticated action or create authenticated session
          return map[string]interface{}{
              "success": true,
              "userId":  response.UserId,
          }
      } else {
          // The user did not complete the challenge successfully
          return map[string]interface{}{
              "error": "Challenge validation failed",
          }
      }
      ```
    </CodeGroup>
  </Tab>

  <Tab title="Pre-built UI">
    <CodeGroup>
      ```ts Node.js theme={null}
      const result = await authsignal.validateChallenge({
        token: "eyJhbGciOiJ..." // Token from redirect URL query parameter
      });

      if (result.state === "CHALLENGE_SUCCEEDED") {
        // The user completed the challenge successfully
        // Proceed with the action
        return { success: true };
      }
      ```

      ```csharp C# theme={null}
      var result = await authsignal.ValidateChallenge(new ValidateChallengeRequest(
          Token: "eyJhbGciOiJ..." // Token from redirect URL query parameter
      ));

      if (result.State == "CHALLENGE_SUCCEEDED") 
      {
          // The user completed the challenge successfully
          // Proceed with the action
          return new { Success = true };
      }
      ```

      ```java Java theme={null}
      ValidateChallengeRequest request = new ValidateChallengeRequest();
      request.token = "eyJhbGciOiJ..."; // Token from redirect URL query parameter

      ValidateChallengeResponse result = authsignal.validateChallenge(request).get();

      if ("CHALLENGE_SUCCEEDED".equals(result.state)) {
          // The user completed the challenge successfully
          // Proceed with the action
          return Map.of("success", true);
      }
      ```

      ```ruby Ruby theme={null}
      result = Authsignal.validate_challenge({
        token: "eyJhbGciOiJ..." # Token from redirect URL query parameter
      })

      if result[:state] == "CHALLENGE_SUCCEEDED"
        # The user completed the challenge successfully
        # Proceed with the action
        { success: true }
      end
      ```

      ```python Python theme={null}
      result = authsignal.validate_challenge(
          token="eyJhbGciOiJ..."  # Token from redirect URL query parameter
      )

      if result["state"] == "CHALLENGE_SUCCEEDED":
          # The user completed the challenge successfully
          # Proceed with the action
          return {"success": True}
      ```

      ```php PHP theme={null}
      $result = Authsignal::validateChallenge([
          'token' => "eyJhbGciOiJ..." // Token from redirect URL query parameter
      ]);

      if ($result["state"] === "CHALLENGE_SUCCEEDED") {
          // The user completed the challenge successfully
          // Proceed with the action
          return ['success' => true];
      }
      ```

      ```go Go theme={null}
      result, err := client.ValidateChallenge(
          ValidateChallengeRequest{
              Token: "eyJhbGciOiJ...", // Token from redirect URL query parameter
          },
      )
      if err != nil {
          return err
      }

      if (result.State == "CHALLENGE_SUCCEEDED") {
          // The user completed the challenge successfully
          // Proceed with the action
          return map[string]interface{}{"success": true}
      }
      ```
    </CodeGroup>
  </Tab>
</Tabs>
