Network rewards, packages, deals & birthday gifts
Four kinds of code that are not part of a merchant's own loyalty campaign but can still be redeemed at your till. Route a scanned or typed code by its prefix:
| Prefix | What it is | Preview | Redeem |
|---|---|---|---|
RWD- |
Network reward bought with coins | qr/validate |
rewards/claim |
PKG- / APT- |
Package voucher / appointment | packages/redeem/preview |
packages/redeem |
DEAL- |
Deal Hunt order paid in the app | item-deals/redeem/preview |
item-deals/redeem |
BDAY- |
Birthday gift voucher | birthday-gift/redeem/preview |
birthday-gift/redeem |
Scope required: transactions.write. Sandbox (rn_test_) keys get 404 on all of these: the sandbox merchant has no such codes.
Unknown codes are capped. The code on the customer's screen is the proof that they are at your till, so guessing is stopped: after 30 codes within 10 minutes that name nothing at your merchant (typos included), every redeem and redeem/preview call below — and every typed RDM- code on qr/validate — from your integration at that merchant answers 429 with error_code: redeem.code_attempts and a Retry-After header until the window ends. A used or expired code is not unknown. Scanned QRs of campaign redemptions are never counted.
Network rewards (RWD-…)
Customers buy network rewards with coins in the Reward Networks app and receive a QR starting with RWD-. A reward is either open to the whole network or owned by one merchant; merchant-owned rewards can only be claimed at that merchant.
Preview
POST /qr/validate with the token answers qr_type: "network_reward":
{
"success": true,
"data": {
"qr_type": "network_reward",
"customer_id": 123,
"network_redemption_id": 881,
"customer": { "user_id": 123, "full_name": "Jane Doe", "is_member": false },
"metadata": {
"reward_name": "$5 off at any partner cafe",
"reward_type": "DISCOUNT",
"points_used": 500,
"discount_amount": 5
}
}
}
Claim
curl -X POST "https://staging.app.rewards.network/api/partner/v1/rewards/claim" \
-H "Authorization: Bearer rn_live_..." \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 9e8d7c6b-5a4f-4e3d-2c1b-0a9f8e7d6c5b" \
-d '{ "qr_token": "RWD-3F9KQ2LM", "purchase_amount": 40.00, "branch_id": 45 }'
purchase_amount lets the API clamp the discount to the sale (a $5 reward on a $3 coffee discounts $3). The response includes discount_amount and final_amount.
Errors come back as 400 with a cashier-readable message: This reward has already been claimed., This QR has expired., This reward can only be claimed at Owner Cafe., This reward is no longer available. The customer's coins have been refunded.
Package vouchers (PKG-…) and appointments (APT-…)
Merchants sell packages (for example "10 washes") in the app. The customer receives a set of voucher codes PKG-XXXX-NNXX (NN is the voucher number); packages that need a booking produce APT-XXXX-XXXX appointment codes instead.
Preview
curl -X POST "https://staging.app.rewards.network/api/partner/v1/packages/redeem/preview" \
-H "Authorization: Bearer rn_live_..." \
-H "Content-Type: application/json" \
-d '{ "code": "PKG-8H2K-03QF" }'
{
"success": true,
"message": "Voucher found.",
"data": {
"voucher": { "voucher_code": "PKG-8H2K-03QF", "seq": 3, "status": "AVAILABLE" },
"package": { "package_id": 12, "name": "10 Car Washes", "banner_url": null, "price_per_voucher": 9.9, "requires_appointment": false },
"customer_id": 123,
"customer_name": "Jane Doe",
"total_vouchers": 10,
"remaining": 8,
"expires_at": "2027-03-31",
"appointment": null,
"allowed_branch_ids": null,
"user_package_id": 301
}
}
Show remaining and expires_at to the cashier. A wrong or used code answers 422 with Code not found. or a similar message.
A package that needs a booking (requires_appointment: true) is redeemed only against a live booking: scan the APT-… code (or the PKG-… code of a booked voucher). A voucher with no booking answers 409 with error_code: BOOKING_REQUIRED and This package needs a booking. Ask the customer to book a time in the app first.; once the customer has booked, the same request (same Idempotency-Key) can be sent again. An appointment left unredeemed about an hour after its time is closed as missed and its voucher goes back to the customer to book again.
Redeem
curl -X POST "https://staging.app.rewards.network/api/partner/v1/packages/redeem" \
-H "Authorization: Bearer rn_live_..." \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d" \
-d '{ "code": "PKG-8H2K-03QF", "branch_id": 45 }'
One voucher of the package is marked used. branch_id is required when the package is limited to some branches (allowed_branch_ids in the preview).
Deal Hunt orders (DEAL-…)
Customers buy item deals ("2× Gula Melaka Latte") in the app and pay there; the order QR is DEAL-XXXX-XXXX (the preview tells you the quantity). Nothing is collected at the till. qr/validate does not read these codes.
Preview
curl -X POST "https://staging.app.rewards.network/api/partner/v1/item-deals/redeem/preview" \
-H "Authorization: Bearer rn_live_..." \
-H "Content-Type: application/json" \
-d '{ "code": "DEAL-7KQ2-X4MV" }'
{
"success": true,
"message": "Order found.",
"data": {
"order": {
"order_id": 5120,
"code": "DEAL-7KQ2-X4MV",
"qty": 2,
"total_paid": 19.8,
"status": "paid",
"valid_until": "2026-11-04",
"redeemed_at": null,
"deal": { "deal_id": 31, "name": "Gula Melaka Latte" }
},
"customer_id": 123,
"customer_name": "Jane Doe",
"allowed_branch_ids": [45, 46]
}
}
The order object is trimmed here; it also carries the price breakdown and the full deal block.
Redeem
curl -X POST "https://staging.app.rewards.network/api/partner/v1/item-deals/redeem" \
-H "Authorization: Bearer rn_live_..." \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 6b1f0e2d-3c4a-4b5c-8d9e-0f1a2b3c4d5e" \
-d '{ "code": "DEAL-7KQ2-X4MV", "branch_id": 45 }'
The order becomes redeemed. branch_id is required when the deal is limited to some branches (allowed_branch_ids not null). Errors answer 422: Code not found., This deal has already been redeemed., This deal QR has expired., Select the branch where this deal is being redeemed., This deal cannot be redeemed at the selected branch.
Birthday gifts (BDAY-…)
A member claims their birthday gift in the app and gets a voucher BDAY-XXXX-NNNN, redeemable until its valid_until date. qr/validate does not read these codes either.
curl -X POST "https://staging.app.rewards.network/api/partner/v1/birthday-gift/redeem/preview" \
-H "Authorization: Bearer rn_live_..." \
-H "Content-Type: application/json" \
-d '{ "code": "BDAY-Q8ZD-4417" }'
The preview answers data.claim (claim_id, code, reward_label, title, status, valid_until, …), customer_id, customer_name and allowed_branch_ids. Redeem with POST /birthday-gift/redeem and { "code": "...", "branch_id": 45 }, with an Idempotency-Key; the same branch rule applies. Errors answer 422: Code not found., This birthday gift has already been redeemed., This birthday voucher has expired., Select the branch where this gift is being redeemed.
Branches
branch_id must be an open branch of the key's merchant (GET /branches); a branch the merchant has closed answers 422.
Reconciliation
These redemptions do not appear in GET /transactions (that feed is points/stamps ledgers). Subscribe to the reward.claimed, package.redeemed, item_deal.redeemed and birthday_gift.redeemed webhooks: they fire whichever till redeemed the code (yours, another partner's or the merchant app). Otherwise reconcile from your own records keyed by the code.
Reward Networks