Reward Networks Reward Networks Developer Hub

Webhooks

Get an HTTPS POST when something happens at a merchant you are linked to, instead of polling the history endpoints. Register endpoints in the Developer Portal → Webhooks.

Events

Event Fired when data fields
transaction.committed Points/stamps awarded for a purchase — by you, another partner, or the merchant app transaction_id, user_id, branch_id, final_amount, points_earned, stamps_earned, qr_scan_id, source
transaction.voided An earn was voided transaction_id, reversal_transaction_id, user_id, developer_id, points_reversed, stamps_reversed
redemption.completed A voucher / points / stamps redemption was completed redemption_id, user_id, branch_id, redemption_type, points_delta, stamps_delta
redemption.approved The customer approved a cashier redemption in the app (Customer approval); complete it before expires_at redemption_id, redemption_code, user_id, redemption_type, expires_at
redemption.declined The customer declined a cashier redemption; it is cancelled redemption_id, redemption_code, user_id, redemption_type
membership.created A customer joined the merchant membership_id, user_id, branch_id
reward.claimed A network reward QR (RWD-) was claimed at the merchant network_redemption_id, reward_id, user_id, branch_id, purchase_amount, discount_amount, final_amount
package.redeemed A package voucher (PKG- / APT-) was redeemed voucher_code, seq, user_package_id, package_id, user_id, branch_id, remaining
item_deal.redeemed A Deal Hunt order (DEAL-) was redeemed order_id, code, deal_id, qty, user_id, branch_id
birthday_gift.redeemed A birthday gift voucher (BDAY-) was redeemed claim_id, code, campaign_id, reward_label, user_id, branch_id

The last four fire whoever redeemed the code — your till, another partner or the merchant app.

Pick the events per endpoint when you create it. New event types are never added to an existing endpoint on their own: edit the endpoint in the portal to subscribe. Events are per merchant, not per key: if you are linked to three merchants you receive all three merchants' events on the same endpoint, your sandbox merchant included. The top-level merchant_id says which merchant an event belongs to and is_sandbox whether it came from your sandbox merchant.

Delivery

POST /webhooks/reward-networks HTTP/1.1
Content-Type: application/json
X-RN-Event: transaction.committed
X-RN-Signature: t=1758537667,v1=5f1d4c...e9

{
  "id": "0c1a3d2e-6b7f-4a8c-9d0e-1f2a3b4c5d6e",
  "event": "transaction.committed",
  "created_at": "2026-09-22T10:41:07+00:00",
  "merchant_id": 17,
  "is_sandbox": false,
  "data": {
    "transaction_id": 9901,
    "user_id": 123,
    "branch_id": 45,
    "final_amount": 43.5,
    "points_earned": 43,
    "stamps_earned": 1,
    "qr_scan_id": "POS-0001-20260922-0042",
    "source": "partner"
  }
}

Respond with any 2xx within 10 seconds. Do the work after responding if it is slow.

id is unique per event. Deliveries can repeat (retries, or the same event to several endpoints), so store the id and ignore duplicates.

Verify the signature

Every delivery is signed with the whsec_… secret shown once when you created the endpoint. Reject anything that fails.

X-RN-Signature: t=<unix timestamp>,v1=<hex HMAC-SHA256 of "<timestamp>.<raw body>">

// PHP
[$t, $v1] = explode(',', $request->header('X-RN-Signature'));
$timestamp = substr($t, 2);
$expected  = hash_hmac('sha256', $timestamp . '.' . $request->getContent(), $secret);
$valid     = hash_equals($expected, substr($v1, 3)) && abs(time() - (int) $timestamp) < 300;
// Node.js
const [t, v1] = req.headers['x-rn-signature'].split(',');
const timestamp = t.slice(2);
const expected = crypto.createHmac('sha256', secret).update(`${timestamp}.${rawBody}`).digest('hex');
const valid = crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(v1.slice(3)))
  && Math.abs(Date.now() / 1000 - Number(timestamp)) < 300;

Use the raw request body, not a re-serialised one. Reject timestamps older than 5 minutes to block replays.

Retries and disabling

Attempt Delay after failure
1 → 2 1 minute
2 → 3 5 minutes
3 → 4 30 minutes
4 → 5 2 hours
5 → 6 6 hours

A delivery fails on a non-2xx, a timeout or a connection error. After 20 consecutive failures the endpoint is disabled automatically; re-enable it in the portal once your side is fixed. The portal shows every delivery with its status and response code (not the response body).

The endpoint must be a public https URL: hostnames that resolve to private, loopback or link-local addresses are refused, and redirects are not followed — answer 2xx directly from the registered URL.

Test it

The Test button in the portal sends a signed ping event to your endpoint immediately. Verify the signature on it exactly as you would on a real event.

Rotate the secret

Delete the endpoint and create it again; the new secret is shown once. There is no in-place rotation, so create the new endpoint first, switch your verifier to accept both secrets, then delete the old one.