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
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.- Custom UI
- Pre-built UI
// 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"
};
}
// 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"
};
}
// 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"
);
}
# 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
# 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"
}
// 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"
];
}
// 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",
}
}
// 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"
};
}
// 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"
};
}
// 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"
);
}
# 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
# 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"
}
// 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"
];
}
// 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",
}
}
- 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.const request = {
userId: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833",
action: "signIn",
idempotencyKey: "83d07321-9bba-4f08-a871-02e2af813b72",
};
const response = await authsignal.getAction(request);
var request = new GetActionRequest(
UserId: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833",
Action: "signIn",
IdempotencyKey: "83d07321-9bba-4f08-a871-02e2af813b72"
);
var response = await authsignal.GetAction(request);
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();
response = Authsignal.get_action(
user_id: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833",
action: "signIn",
idempotency_key: "83d07321-9bba-4f08-a871-02e2af813b72")
response = authsignal.get_action(
user_id="dc58c6dc-a1fd-4a4f-8e2f-846636dd4833",
action="signIn",
idempotency_key="83d07321-9bba-4f08-a871-02e2af813b72"
)
$response = Authsignal::getAction([
'userId' => "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833",
'action' => "signIn",
'idempotencyKey' => "83d07321-9bba-4f08-a871-02e2af813b72"
]);
response, err := client.GetAction(
GetActionRequest{
UserId: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833",
Action: "signIn",
IdempotencyKey: "83d07321-9bba-4f08-a871-02e2af813b72",
},
)
Query actions
Retrieve a list of actions for a specific user to view their authentication history.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);
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);
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();
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
)
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
)
$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
]);
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
},
)
Update action
Manually modify the state of a previously tracked action. This is useful for administrative actions or custom workflows.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);
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);
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();
updatedAttributes = Authsignal.update_action(
user_id: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833",
action: "signIn",
idempotency_key: "83d07321-9bba-4f08-a871-02e2af813b72",
attributes: {
state: "REVIEW_REQUIRED"
})
updatedAttributes = authsignal.update_action(
user_id="dc58c6dc-a1fd-4a4f-8e2f-846636dd4833",
action="signIn",
idempotency_key="83d07321-9bba-4f08-a871-02e2af813b72",
attributes={
"state": "REVIEW_REQUIRED"
}
)
$attributes = array(
"state" => "REVIEW_REQUIRED",
);
$updatedAttributes = Authsignal::updateAction(
userId: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833",
action: "signIn",
idempotencyKey: "83d07321-9bba-4f08-a871-02e2af813b72",
attributes: $attributes);
updatedAttributes, err := client.UpdateAction(
UpdateActionRequest{
UserId: "dc58c6dc-a1fd-4a4f-8e2f-846636dd4833",
Action: "signIn",
IdempotencyKey: "83d07321-9bba-4f08-a871-02e2af813b72",
Attributes: &ActionAttributes{
State: "REVIEW_REQUIRED",
},
},
)
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: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 thecustom field to pass business-specific data for use in rules:
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"
}
}
});
Action lifecycle
Understanding the complete action lifecycle helps you implement robust authentication flows:1. Action creation
Actions are created when you calltrack(). 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)
ForCHALLENGE_REQUIRED actions:
- Generate short-lived token or URL
- User completes authentication via pre-built UI or Client SDKs
- Action state updates to
CHALLENGE_SUCCEEDEDorCHALLENGE_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: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: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);
}

