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.
Status
Section titled “Status”| Status | Meaning | Important distinction |
|---|---|---|
completed | The pipeline ran to the end. | Says nothing about rewards. Zero rules may have matched, and individual actions may have status: "failed". |
failed | An 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 / running | Defined in the schema and accepted as filters. | Event processing does not currently write these. Until it finishes, the workflow simply does not exist. |
Endpoints
Section titled “Endpoints”All are under /projects/{projectId} and accept a project API key or dashboard token.
| Endpoint | Returns |
|---|---|
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}/full | The same full record. |
GET /workflows/stats | { total, pending, running, completed, failed, avgDurationMs }. |
GET /users/{userId}/events | The customer’s event history (see below). |
Full record
Section titled “Full record”{ "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:
ruleslists only candidate rules: active, inside their validity window, and bound to this event type (or unbound). A rule missing fromrulesnever ran. A rule present withmatched: falsehad its conditions evaluate false.conditionResultsis empty during live processing. To see per-condition values, rerun the conditions throughPOST /projects/{projectId}/rules/testwithtriggerDataand the relevantresolvedContextentries.actions[].payloadshows the config after expressions were evaluated, which is where unit and path mistakes show up.actions[].errorexplains failures such asReward action requires userIdorHTTP 500.resources[].status: "failed"witherrormeans a data source failed. Conditions that depend on it evaluate false.resolvedContextis the data the rules saw.
Customer event history
Section titled “Customer event history”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.
Polling
Section titled “Polling”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
404as “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.
See also
Section titled “See also”Loyalty events · Sending business events · Debugging rules · Workflows and External Users in the API reference