Skip to content

Workflows

Each ingested event produces one workflow. The workflow id is the event id (evt_…) returned by POST /ingest/events. The record is written once, when processing finishes. It shows the trigger data, the resources resolved, every candidate rule with its match result, and every action with its result.

StatusMeaningImportant distinction
completedThe pipeline ran to the end.Says nothing about rewards. Zero rules may have matched, and individual actions may have status: "failed".
failedAn action threw. Examples: insufficient balance on redemption, a wallet or storage error.error holds the message and actions is empty. Actions before the failing one may already have taken effect. Check the wallet before re-sending.
pending / runningDefined in the schema and accepted as filters.Event processing does not currently write these. Until it finishes, the workflow simply does not exist.

All are under /projects/{projectId} and accept a project API key or dashboard token.

EndpointReturns
GET /workflows{ items, total }, newest first. Items have id, eventType, status, resolvedUserId, startedAt, completedAt, createdAt. Filters: status, eventType, eventId, resolvedUserId, dateFrom, dateTo, limit (1–100, default 50), offset.
GET /workflows/{workflowId}Full record (below), or 404 with error.code: 3400.
GET /workflows/{workflowId}/fullThe same full record.
GET /workflows/stats{ total, pending, running, completed, failed, avgDurationMs }.
GET /users/{userId}/eventsThe customer’s event history (see below).
{
"id": "evt_abc", "eventId": "evt_abc", "eventType": "purchase", "status": "completed",
"triggerData": { "type": "purchase", "userId": "cust_8421", "payload": { "orderId": "ord_1001", "amount": 7450 } },
"resolvedUserId": "cust_8421", "error": null,
"startedAt": "…", "completedAt": "…", "createdAt": "…",
"steps": [
{ "stepType": "trigger", "durationMs": 0, "status": "completed" },
{ "stepType": "resource_resolution", "durationMs": 4, "status": "completed" },
{ "stepType": "rule_matching", "durationMs": 0, "status": "completed" },
{ "stepType": "action_execution", "durationMs": 21, "status": "completed" }
],
"resources": [{ "key": "wallet", "name": "Wallet", "type": "internal", "status": "completed", "durationMs": 3 }],
"rules": [{ "ruleId": "rule_…", "name": "100 points for purchases of $50+", "priority": 0, "matched": true, "conditionResults": [] }],
"actions": [{
"ruleId": "rule_…", "actionType": "reward", "status": "completed", "durationMs": 18,
"payload": { "userId": "cust_8421", "assetType": "points", "amount": 100 },
"result": { "amount": 100, "assetType": "points", "userId": "cust_8421", "expiresAt": null }
}],
"resolvedContext": { "event": { "…": "…" }, "wallet": { "…": "…" } }
}

How to read it:

  • rules lists only candidate rules: active, inside their validity window, and bound to this event type (or unbound). A rule missing from rules never ran. A rule present with matched: false had its conditions evaluate false.
  • conditionResults is empty during live processing. To see per-condition values, rerun the conditions through POST /projects/{projectId}/rules/test with triggerData and the relevant resolvedContext entries.
  • actions[].payload shows the config after expressions were evaluated, which is where unit and path mistakes show up. actions[].error explains failures such as Reward action requires userId or HTTP 500.
  • resources[].status: "failed" with error means a data source failed. Conditions that depend on it evaluate false.
  • resolvedContext is the data the rules saw.

GET /projects/{projectId}/users/{userId}/events returns { items, total } (limit/offset). Each item has id (event id), eventType, status, payload, rules, actions, createdAt, processedAt. payload, rules and actions are JSON strings, so parse them. The item is created at ingest time with status: "completed" and rules: null, then filled in after processing. Use rules/actions being non-null, not status, to tell whether it was processed. Only events sent with userId appear here.

async function waitForWorkflow(projectId: string, eventId: string, token: string) {
const url = `https://api.storelayer.io/projects/${projectId}/workflows/${eventId}`
for (let attempt = 0, delay = 250; attempt < 10; attempt++, delay = Math.min(delay * 2, 4000)) {
const res = await fetch(url, { headers: { Authorization: `Bearer ${token}` } })
if (res.ok) return (await res.json()).data // full record
if (res.status !== 404) throw new Error(`workflow lookup failed: ${res.status}`)
await new Promise((r) => setTimeout(r, delay))
}
return null // still not processed: investigate, do not re-ingest
}
  • Treat 404 as “not finished yet”. Typical processing is quick, but it is queued, so allow for some seconds.
  • GET /workflows?eventId=evt_… can show the list row a moment before the detail is readable. Poll the detail endpoint.
  • A timeout is not a failure. Re-sending the event creates a second workflow and can award twice.

Loyalty events · Sending business events · Debugging rules · Workflows and External Users in the API reference