Webhooks

The Loyalty Engine sends webhook events to your endpoint when key actions occur. Use webhooks to update your own systems in real time — notify a member when points are awarded or a reward is issued, trigger CRM updates, or sync loyalty data to your data warehouse.

Available events

EventDescription
transaction.processedPoints awarded after a successful evaluate() call
transaction.redeemedPoints redeemed, discount issued after a successful redeem() call
member.enrolledNew member auto-created on their first evaluate() call
member.tier_changedMember crossed a tier threshold; tier updated automatically
member.stamp_card_completedMember completed all stamps on a stamp-card campaign
campaign.budget_alertCampaign spend crossed the configured alert threshold
campaign.budget_suspendedCampaign automatically paused after hitting its budget cap
points.expiredPoints expired via the nightly expiry worker
reward.issuedTangible reward issued — fulfilment worker generated the code and updated state to ISSUED

Webhook payload

All webhook events share a common envelope structure. The data field contains the event-specific payload.

Payload
{
  "deliveryId": "whe_01HX...",
  "event":      "transaction.processed",
  "tenantId":   "tenant-uuid",
  "timestamp":  "2026-01-15T10:30:00.000Z",
  "data": {
    "transactionId":  "d2f3a1b4-...",
    "transactionRef": "txn_pos_12345",
    "memberId":       "9f4c2e8a-...",
    "pointsAwarded":  150,
    "pointsBalance":  650,
    "campaigns": [
      {
        "campaignId":   "5a7b3c9d-...",
        "campaignName": "Spend & Earn",
        "pointsEarned": 150,
        "rewardIds":    []
      }
    ]
  }
}

Delivery & retries

Webhooks are delivered via HTTPS POST. Your endpoint must return a 2xx status within 10 seconds. If it does not, the engine retries with exponential backoff:

  • Attempt 1 — immediately
  • Attempt 2 — 1 minute later
  • Attempt 3 — 5 minutes later
  • Attempt 4 — 30 minutes later
  • Attempt 5 — 2 hours later (final)

After 5 failed attempts the event is marked as failed and no further retries occur. You can view delivery logs and manually replay events from the merchant portal.

HMAC signature verification

Every webhook includes an X-Loyalty-Signature header containing an HMAC-SHA256 signature. Always verify this signature before processing the event.

verify-webhook.ts
import crypto from 'node:crypto';

export function verifyWebhookSignature(
  payload: string,
  signature: string,
  secret: string,
): boolean {
  const expected = crypto
    .createHmac('sha256', secret)
    .update(payload)
    .digest('hex');

  // Constant-time comparison — prevents timing attacks
  return crypto.timingSafeEqual(
    Buffer.from(expected),
    Buffer.from(signature),
  );
}

// In your webhook handler:
app.post('/webhooks/loyalty', (req, res) => {
  const sig = req.headers['x-loyalty-signature'] as string;
  const raw = JSON.stringify(req.body); // use raw body string

  if (!verifyWebhookSignature(raw, sig, process.env.WEBHOOK_SECRET!)) {
    return res.status(401).json({ error: 'Invalid signature' });
  }

  const event = req.body;
  // Handle event...
  res.status(200).end();
});
Webhooks — Loyalty Engine Docs