Triggers API
Endpoints
/workspaces/:wid/triggersList triggers in a workspace/workspaces/:wid/triggersCreate a trigger/triggers/:idUpdate a trigger/triggers/:idDelete a trigger/workspaces/:wid/triggers/debugExplain how triggers evaluate an event/workspaces/:wid/triggers/debug-historyLast 100 debug evaluations/sdk/eventsTrack an event (SDK endpoint)Managing triggers requires a workspace owner or admin. The list is ordered by priority (highest first), then newest first, and returned as { "triggers": [...] }. Creating, updating and deleting a trigger dispatches the trigger.created, trigger.updated and trigger.deleted webhooks.
Trigger Object
{
"id": "trig_xxxx",
"workspaceId": "ws_xxxx",
"surveyId": "survey_yyyy",
"eventName": "match_completed",
"conditions": [
{
"field": "result",
"operator": "equals",
"value": "win"
}
],
"priority": 1,
"frequencyRule": "ONCE_PER_CONTEXT",
"contextKey": "matchId", // ONCE_PER_CONTEXT only
"frequencyDays": null, // ONCE_PER_X_DAYS only; 30 when null
"cooldownSeconds": 86400, // 24 hours
"maxDisplaysPerUser": 3,
"maxResponsesPerUser": 1,
"sampleRate": 1,
"holdoutRate": 0,
"environment": "PRODUCTION",
"isActive": true,
"startsAt": null,
"endsAt": null,
"createdAt": "2026-10-01T09:00:00.000Z",
"updatedAt": "2026-10-01T09:00:00.000Z"
}Fields
| Field | Type | Description |
|---|---|---|
surveyId | string | Required on create. Must belong to the same workspace |
eventName | string | Required on create. Event name the SDK sends, e.g. match_completed or $pageview |
conditions | array | { field, operator, value } rules; all must match. Default [] |
priority | integer | Higher is evaluated first. Default 0 |
frequencyRule | enum | ONCE_EVER, ONCE_PER_USER (default), ONCE_PER_CONTEXT, ONCE_PER_X_DAYS or UNLIMITED |
contextKey | string | null | Event property identifying the context for ONCE_PER_CONTEXT. Letters, numbers and _ . : -, up to 80 characters |
frequencyDays | integer | null | Days between displays for ONCE_PER_X_DAYS, 1 to 365. 30 when null |
cooldownSeconds | integer | null | Minimum seconds between displays to the same person |
maxDisplaysPerUser | integer | null | Display limit per person; null for no limit |
maxResponsesPerUser | integer | null | Completed-response limit per person; null for no limit |
sampleRate | number | 0 to 1. Share of eligible people who can see the survey. Default 1 |
holdoutRate | number | 0 to 0.95. Share of people held out as a control group. Default 0 |
environment | enum | PRODUCTION (default), STAGING or DEVELOPMENT |
isActive | boolean | Default true |
startsAt, endsAt | ISO datetime | null | Optional schedule window |
Response-based rules (ONCE_EVER, ONCE_PER_USER, maxResponsesPerUser) count completed responses for identified users. Display-based rules (cooldownSeconds, ONCE_PER_CONTEXT, ONCE_PER_X_DAYS, maxDisplaysPerUser) count displays the server records. Anonymous visitors are capped by displays for every rule. See the trigger engine guide for the full semantics.
Create a Trigger
POST /workspaces/:wid/triggers
{
"surveyId": "survey_yyyy",
"eventName": "match_completed",
"conditions": [
{ "field": "sport", "operator": "equals", "value": "football" }
],
"frequencyRule": "ONCE_PER_CONTEXT",
"contextKey": "matchId",
"priority": 1,
"cooldownSeconds": 3600,
"isActive": true
}
// 201 Created
{ "trigger": { ...trigger object } }PATCH /triggers/:id accepts any subset of the same fields and returns { "trigger": ... }. DELETE /triggers/:id returns 204.
Debug an Event
POST /workspaces/:wid/triggers/debug
{
"eventName": "match_completed",
"properties": { "matchId": "match_789", "result": "win" },
"externalUserId": "user_123", // or "anonymousId": "anon_..."
"environment": "PRODUCTION"
}
// 200 OK
{
"result": [
{
"triggerId": "trig_xxxx",
"surveyId": "survey_yyyy",
"surveyTitle": "Post-match feedback",
"eventName": "match_completed",
"matched": false,
"reasons": ["Survey was already shown for matchId = match_789."],
"conditions": [{ "field": "result", "operator": "equals", "expected": "win", "actual": "win", "passed": true, ... }]
}
]
}The debugger checks every trigger for the event and environment, including inactive ones, and stores the evaluation in the debug history. It never records a display.
Track an Event (SDK endpoint)
POST /sdk/events
// Headers:
// X-Project-Key: pk_live_xxxx
{
"event": "match_completed",
"properties": {
"matchId": "match_789",
"result": "win"
},
"externalUserId": "user_123", // identified user (optional)
"anonymousId": "anon_k3j9x...", // persisted visitor id (optional, max 200 chars)
"traits": { "plan": "pro" }, // matched as trait.plan
"context": { "page": "/dashboard" }, // matched as context.page
"environment": "PRODUCTION" // default PRODUCTION
}
// 200 OK — returns eligible surveys, highest priority first
{
"surveys": [
{
"id": "survey_yyyy",
"slug": "post-match-feedback",
"workspaceSlug": "acme"
}
]
}anonymousId is a top-level field. When there is no externalUserId, sampling, holdouts and frequency caps apply per anonymousId. The browser SDK generates and sends it automatically. When at least one survey is returned, the server records a display of the first one for this user or visitor in the same request, so the caps hold even if the client never opens it.
Condition Operators
"equals" | "not_equals" | "contains" | "not_contains" "greater_than" | "less_than" "exists" | "not_exists" "in_list" | "not_in_list"