Reward Networks Reward Networks Developer Hub

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, stamps are null when the merchant has no running campaign of that kind.
  • A loyalty campaign can offer any combination of points.amount (fixed discount: point_value off per min_points points, at least min_points), points.percent (percent off per points_required points) and points.items (catalog items with points_required and redeemable). Options the merchant has not switched on are null / empty — show the customer only the filled ones.
  • pending lists redemptions the customer already created in the app but never showed — "I made the QR at home". Pick one up with type: pending instead of creating a new one. While approval_required is true its redemption_code is null: 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 with transactions/redeem.
  • pin_required tells you whether Step 3 must include the customer's PIN. It is a platform-wide setting.
  • approval_required tells 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_app says whether this customer can approve (signed in to the Rewards app on a phone; null while approval is off); customer.has_pin whether a PIN can be used instead.
  • pin_accepted says whether a correct PIN is taken as proof for this customer right now: false when 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.