Skip to content

Sending Business Events

Send one event per authoritative business fact, from your backend, after it is final (payment captured, account created). Storelayer queues the event and processes it asynchronously against your rules. See Loyalty events for the model.

Terminal window
curl -X POST https://api.storelayer.io/ingest/events \
-H "X-API-Key: $STORE_LAYER_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"type": "purchase",
"userId": "cust_8421",
"payload": {
"orderId": "ord_1001",
"amount": 7450,
"currency": "USD",
"items": [{ "sku": "COFFEE-1KG", "quantity": 2, "price": 3725 }]
}
}'
  • Auth: project API key via X-API-Key or Authorization: Bearer <key>. No projectId in the path.
  • Body: type (non-empty string, required), userId (string, optional), payload (object, required).
  • Only those three top-level fields survive. Anything else (amount, eventId, idempotencyKey, timestamp) is silently stripped. Put business data in payload.
DecisionDoWhy
Event typeStable snake_case: purchase, order_completed, user_registeredAn event resource key must match ^[a-zA-Z][a-zA-Z0-9_]*$ to bind a rule to the type.
MoneyInteger minor units (7450 = $74.50) plus currencyRule thresholds compare raw numbers. 74.50 vs 5000 silently never matches.
CustomerYour stable external ID in userIdWallet actions default to $('event').userId. Without it, reward fails with Reward action requires userId.
Order identitypayload.orderIdLets rules tag wallet entries and lets you trace and deduplicate.
ReservedNever send payload._metaUsed internally for event chaining.

201 Created:

{ "success": true, "data": { "id": "evt_x7Kq…", "status": "accepted" } }

Store data.id. It is also the workflow id.

400 validation failure. Note that error is an array of issues here, not the { code, message } object other endpoints return:

{
"success": false,
"error": [{ "expected": "string", "code": "invalid_type", "path": ["userId"], "message": "Invalid input: expected string, received number" }],
"data": { "type": "purchase", "userId": 123, "payload": {} }
}

data echoes the body you sent.

401 (error.code: 1002) means the API key is missing or invalid.

There is no idempotency key. Each call gets a new evt_ id and is processed independently, so the same purchase sent twice earns twice. Wallet entries created by rules reference the rule, not your order.

  • Deduplicate at the source: record “sent to Storelayer” against your order inside the same transaction or outbox that triggers the send.
  • On a timeout or network error, do not resend blindly. If you saved the evt_ id, poll GET /projects/{projectId}/workflows/{eventId}. If the request never returned an id, check the customer’s recent events (GET /projects/{projectId}/users/{userId}/events) or wallet transactions before retrying.
  • Put orderId in a reward’s tags ("tags": ["{{ $('event').payload.orderId }}"]) so you can audit the ledger for duplicates.

Send a separate event, such as refund with the original orderId and refunded amount in cents, and give it its own rule with a redemption action that mirrors the earning formula. If the customer has already spent the points, the redemption fails (Insufficient balance…) and that workflow is failed. Handle that case in your business logic, for example by allowing it or by reversing a smaller amount. See the loyalty program guide.

  1. 201 means queued only. No rule has run yet.
  2. Poll GET /projects/{projectId}/workflows/{eventId}. It returns 404 until processing finishes, then the full record.
  3. Check rules[].matched and actions[].status/error, then confirm the wallet.

See Workflows for the record shape and polling.

Loyalty events · Workflows · Debugging rules · Events and Workflows in the API reference