Redeem without the phone
For customers who cannot show a QR: the cashier finds the customer, picks what to redeem on the till, and the redemption is completed like a scanned one. When the platform asks for proof that the customer is at the till, they approve it in the Rewards app, or type their Rewards PIN on your terminal (see Customer approval).
lookup customer ──► GET redeem-options ──► POST redemptions (creates the hold) ──► [customer approves in the app] ──► POST /transactions/redeem
Scopes: customers.read for the first two calls, transactions.write for the rest.
Step 1: Find the customer
By phone, email or their customer code:
curl "https://staging.app.rewards.network/api/partner/v1/customers/lookup?phone=0123456789" -H "Authorization: Bearer rn_live_..."
Note data.user_id. Details in Customers & members.
Step 2: What can they redeem?
curl "https://staging.app.rewards.network/api/partner/v1/customers/123/redeem-options" -H "Authorization: Bearer rn_live_..."
{
"success": true,
"data": {
"customer": { "user_id": 123, "full_name": "Jane Doe", "has_pin": true, "can_approve_in_app": true },
"pin_required": false,
"approval_required": false,
"pin_accepted": true,
"pin_max_points": null,
"pin_daily_max_points": null,
"points": {
"campaign_id": 17,
"balance": 640,
"on_hold": 0,
"amount": { "point_value": 0.01, "min_points": 100 },
"percent": null,
"items": []
},
"stamps": {
"campaign_id": 18,
"balance": 5,
"rewards": [
{ "milestone_id": 3, "reward_name": "Free pastry", "stamps_required": 5, "redeemable": true }
]
},
"vouchers": [
{ "campaign_id": 22, "title": "10% off any coffee", "discount_percent": 10, "discount_amount": 0, "min_purchase_amount": 5, "max_discount_amount": 3, "redeemable": true }
],
"pending": [
{ "redemption_id": 5512, "redemption_code": "RDM-7K2M9Q", "redemption_type": "VOUCHER", "reward_name": "10% off any coffee", "expires_at": "2026-09-22T11:30:00+00:00" }
]
}
}
points,stampsarenullwhen the merchant has no running campaign of that kind.- A loyalty campaign can offer any combination of
points.amount(fixed discount:point_valueoff permin_pointspoints, at leastmin_points),points.percent(percentoff perpoints_requiredpoints) andpoints.items(catalog items withpoints_requiredandredeemable). Options the merchant has not switched on arenull/ empty — show the customer only the filled ones. pendinglists redemptions the customer already created in the app but never showed — "I made the QR at home". Pick one up withtype: pendinginstead of creating a new one. Whileapproval_requiredistrueitsredemption_codeisnull: the code is the customer's proof that they are at the till. Ask them to read it from their screen (or scan the QR), or pick the redemption up with their PIN — that answer carries the code — and send it withtransactions/redeem.pin_requiredtells you whether Step 3 must include the customer's PIN. It is a platform-wide setting.approval_requiredtells you whether a redemption created without a PIN waits for the customer's approval in the app (Step 4). Also platform-wide.customer.can_approve_in_appsays whether this customer can approve (signed in to the Rewards app on a phone;nullwhile approval is off);customer.has_pinwhether a PIN can be used instead.pin_acceptedsays whether a correct PIN is taken as proof for this customer right now:falsewhen no proof is asked at all, or when the platform reserves the PIN for customers without the app and this one can approve in it.pin_max_points/pin_daily_max_points: the most points one PIN-confirmed redemption may spend, and all of this customer's PIN-confirmed redemptions in a day (null= no cap). Above them, ask for the approval in the app or scan the QR.
Step 3: Create the redemption
curl -X POST "https://staging.app.rewards.network/api/partner/v1/customers/123/redemptions" \
-H "Authorization: Bearer rn_live_..." \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 6b1e8d2f-3c4a-4e5f-8a9b-0c1d2e3f4a5b" \
-d '{ "type": "stamp", "milestone_id": 3 }'
type |
Extra field | Redeems |
|---|---|---|
points_amount |
points |
A fixed number of points as a discount. |
points_percent |
points |
Points as a percentage discount. |
points_item |
item_id |
A catalog item paid with points. |
stamp |
milestone_id |
A stamp-card reward. |
voucher |
campaign_id |
A voucher campaign. |
pending |
redemption_id |
Picks up an existing pending redemption. |
Add "pin": "1234" when pin_required is true — the customer types it on your terminal, the cashier must not know it. Five wrong PINs lock the customer for 15 minutes. When approval_required is true the PIN is optional: send it when the customer has no phone with them, leave it out to ask for their approval in the app. A PIN-confirmed redemption is capped (pin_max_points per redemption, pin_daily_max_points per customer per day, both in redeem-options): above that it answers 422 PIN_LIMIT_EXCEEDED and nothing is held — ask for the approval or scan the QR instead. When pin_accepted is false a PIN answers 422 APPROVAL_REQUIRED.
The response is the same payload qr/validate gives for a scanned QR, plus redemption_code, created_by_cashier and the confirmation fields (confirmation_status: null when no proof was asked, pin, awaiting — see Step 4 — or approved when you pick up a request the customer already approved):
{
"success": true,
"message": "Redemption created successfully",
"data": {
"qr_type": "redemption",
"customer_id": 123,
"redemption_id": 5530,
"redemption_type": "STAMPS",
"metadata": { "reward_name": "Free pastry", "stamps_required": 5, "...": "..." },
"redemption_code": "RDM-4P8ZQ2",
"created_by_cashier": true,
"confirmation_status": null,
"confirmation_expires_at": null
}
}
From here your till continues exactly as after a scan: apply the reward, take payment, then POST /transactions/redeem with redemption_id and the full bill (before the discount) as amount — see Redeem a customer QR. For a picked-up redemption (type: pending) also send the redemption_code of this answer while approval is on.
The points/stamps/voucher are on hold from this moment (they show up in on_hold), so the customer cannot spend them twice while the sale is open.
Step 4: Customer approval
Only when approval_required is true and you created the redemption without a pin. The answer is then 202 with confirmation_status: "awaiting":
{
"success": true,
"message": "Waiting for the customer to approve in the Rewards app",
"data": {
"qr_type": "redemption",
"redemption_id": 5530,
"redemption_type": "STAMPS",
"metadata": { "reward_name": "Free pastry", "...": "..." },
"redemption_code": "RDM-4P8ZQ2",
"created_by_cashier": true,
"confirmation_status": "awaiting",
"confirmation_expires_at": "2026-09-22T10:42:07+00:00"
}
}
The customer's phone shows the request ("Kopi X wants to redeem Free pastry (5 stamps) for you at the till. Approve or decline in the app within 60 seconds."); with notifications turned off they see it when they open the app. They have 60 seconds (confirmation_expires_at) to answer. Show the cashier a waiting screen and either poll every 2–3 seconds:
curl "https://staging.app.rewards.network/api/partner/v1/customers/123/redemptions/5530" -H "Authorization: Bearer rn_live_..."
{
"success": true,
"data": {
"redemption_id": 5530,
"redemption_code": "RDM-4P8ZQ2",
"status": "PENDING",
"confirmation_status": "approved",
"confirmation_expires_at": "2026-09-22T10:42:07+00:00",
"expires_at": "2026-09-22T10:56:31+00:00"
}
}
or subscribe to the redemption.approved / redemption.declined webhooks.
confirmation_status |
Meaning | Next |
|---|---|---|
awaiting |
No answer yet. | Keep waiting until confirmation_expires_at. |
approved |
The customer approved. | Complete with POST /transactions/redeem within 15 minutes (expires_at). |
declined |
The customer declined. status is CANCELLED, the hold is released. |
Tell the cashier; do not retry without asking the customer. |
expired |
No answer in time. status is CANCELLED (or EXPIRED once our 5-minute cleanup has run), the hold is released. |
Create it again, or use the PIN. |
cancelled |
Cancelled before the customer answered: by you, or because the customer left the program or the campaign ended. status is CANCELLED. |
Stop waiting. |
Check status first: anything but PENDING (or COMPLETED after your redeem) means it is over, whatever confirmation_status says — the customer can also cancel an approved redemption from their app before you complete it.
transactions/redeem answers 409 (error_code: redeem.approval_pending) until the customer approves — from any till, the merchant app included. A 409 is never stored by the idempotency layer, so retrying with the same Idempotency-Key after the approval goes through. qr/validate of its RDM- code answers 400 while it waits.
No phone with them? Create it with "pin" instead: a correct PIN confirms it at once (200, confirmation_status: "pin"), no approval asked. To switch while a request is waiting, cancel it and create it again with the PIN. A customer who is not signed in to the app on any phone (can_approve_in_app: false) can only use the PIN or their QR. The PIN caps and pin_accepted from redeem-options apply (Step 3).
type: pending (a redemption the customer made in the app) needs the PIN while approval is on: with their phone at hand they show its QR (or read its code) instead. The answer carries its redemption_code, which transactions/redeem needs for a redemption made in the app while approval is on. A request the customer already approved is picked up without it.
Send an Idempotency-Key with the create: a retry after a timeout then answers with the same redemption instead of asking the customer twice. A 4xx answer is kept for its key too, so after APPROVAL_UNAVAILABLE, a wrong PIN or any other refusal send the next attempt with a new key.
If the sale is abandoned
Release the hold:
curl -X POST "https://staging.app.rewards.network/api/partner/v1/customers/123/redemptions/5530/cancel" -H "Authorization: Bearer rn_live_..."
Only redemptions created by a cashier at your merchant can be cancelled this way; app-created ones expire on their own. A request still waiting for approval can be cancelled too; the customer's phone then can no longer approve it.
PIN and approval errors
| Status | code |
Meaning |
|---|---|---|
422 |
PIN_NOT_SET |
Customer has not set a Rewards PIN in the app yet. They must set it there first. |
422 |
PIN_INVALID |
Wrong PIN. |
429 |
PIN_LOCKED |
Too many wrong attempts; try again in 15 minutes. |
422 |
APPROVAL_UNAVAILABLE |
Approval is required but the customer is not signed in to the Rewards app on a phone. Ask for their PIN, or redeem with their QR. |
422 |
PIN_REQUIRED |
type: pending while approval is required: send the PIN, or scan the redemption's QR. |
422 |
PIN_LIMIT_EXCEEDED |
The PIN-confirmed redemption would spend more than pin_max_points, or take the customer past pin_daily_max_points for the day. Nothing is held. Ask for the approval in the app, or scan their QR. |
422 |
APPROVAL_REQUIRED |
pin_accepted is false: this customer can approve in the Rewards app, so the PIN is not taken. Create the redemption without pin, or scan their QR. |
Reward Networks