Loyalty Events
Your backend sends business events (purchase, user_registered, refund) to POST /ingest/events. Each event is queued and then evaluated against the project’s active rules, and the actions of every matching rule run (reward, redemption, integration, …). Every processed event leaves a workflow record, whose id is the event id, showing what matched and what each action did.
Event shape
Section titled “Event shape”POST https://api.storelayer.io/ingest/eventsX-API-Key: <project API key>Content-Type: application/json
{ "type": "purchase", "userId": "cust_8421", "payload": { "orderId": "ord_1001", "amount": 7450, "currency": "USD" }}| Field | Required | Notes |
|---|---|---|
type | yes | Non-empty string. Rules bind to it. |
userId | no | String. Your stable external customer ID. Wallet actions default to it. |
payload | yes | JSON object (can be {}). Rules read it as $('event').payload.<field>. |
- Authentication: a project API key in
X-API-KeyorAuthorization: Bearer <key>. The project comes from the key, so the path has noprojectId. - Unknown top-level fields are silently dropped. They are not rejected.
{"type":"purchase","payload":{},"amount":7450}is accepted withamountdiscarded, and no rule ever sees it. Put all business data insidepayload. - A missing
type/payloador a wrong type (for exampleuserId: 123) returns400. - There is no idempotency key. Every call creates a new event id. See Sending business events.
payload._metais reserved for event chaining (emit_event). Do not send it.
Response (201): { "success": true, "data": { "id": "evt_…", "status": "accepted" } }.
Processing model
Section titled “Processing model”- Ingest validates the body, assigns
evt_…, enqueues it and returns201. Nothing has been evaluated yet. - A worker loads the active rules whose event binding matches
type(plus rules with no event-type binding), resolves only the resources each rule declares, and evaluates its conditions. - The actions of every matched rule run in order. Rules are sorted by
priority(highest first), then by creation time. Rules bound to a specific type run before unbound ones. There is no stop-processing flag: every matching rule fires. - One workflow is saved with status
completedorfailed. See Workflows.
A 201 only means the event was queued. A reward is confirmed only by the workflow’s action result and the wallet.
Rule anatomy
Section titled “Rule anatomy”{ "name": "100 points for purchases of $50+", "conditions": { "combinator": "AND", "conditions": [ { "leftValue": "{{ $('event').payload.amount }}", "operator": "gte", "rightValue": 5000, "rightType": "number" } ] }, "resources": [{ "id": "<event resource id>", "key": "purchase", "type": "event", "value": {} }], "actions": [{ "type": "reward", "config": { "assetType": "points", "amount": 100 } }], "priority": 0}| Part | Rule |
|---|---|
conditions | At least one condition. combinator is AND or OR (a single flat group). Operators and expression syntax are covered in Conditions and resources. |
resources | Must include at least one event resource. Every key must be registered on the project first (POST /projects/{projectId}/resources) with the same type. |
actions | One action or an array. Types: reward, redemption, modify, integration, apply_referral, complete_referral, mark_code_used, emit_event, update_user, custom_table. Config fields are listed in the loyalty program guide. |
priority | Integer, default 0. It sets the order only. It never suppresses other rules. |
validFrom / validTo | Optional ISO timestamps. Outside the window the rule is skipped. |
isActive | true on create. Change it with PATCH /projects/{projectId}/rules/{ruleId}. |
How a rule binds to an event type
Section titled “How a rule binds to an event type”Choose one of two ways:
| Binding | How | Important distinction |
|---|---|---|
| Event resource key = event type | Register an event resource with key: "purchase" and declare it on the rule | Keys must match ^[a-zA-Z][a-zA-Z0-9_]*$. Use order_completed, not order.completed. |
Generic key event + type condition | Declare { "key": "event", "type": "event" } and add {"leftValue": "{{ $('event').type }}", "operator": "equals", "rightValue": "purchase"} | Works for any type string. |
A rule with neither runs for every event type. Event data is always read as $('event'), whatever the event resource is called. $('purchase') is undefined.
Resources must be declared on the rule
Section titled “Resources must be declared on the rule”During live processing a rule sees only event plus the resources listed in its own resources array. If a condition uses $('wallet') and the rule does not declare wallet, the value is undefined and the condition is false, with no error. The REST API does not infer or auto-create resources for rules.
See also
Section titled “See also”- Setting up a loyalty program: end-to-end rule, test, ingest, verify
- Sending business events
- Workflows · Debugging rules
- Events, Rules and Resources in the API reference