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.
All webhook events share a common envelope structure. The data field contains the event-specific 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": []
}
]
}
}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:
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.
Every webhook includes an X-Loyalty-Signature header containing an HMAC-SHA256 signature. Always verify this signature before processing the event.
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();
});