Reward Networks Reward Networks Developer Hub

Award points & stamps

The core flow of a loyalty-enabled sale. Three calls: identify the customer, preview, commit.

customer shows QR ──► POST /qr/validate ──► POST /transactions/preview ──► customer pays ──► POST /transactions/commit

Scope required: transactions.write.

Step 1: Identify the customer

Scan the QR shown in the customer's Reward Networks app and send the raw string:

curl -X POST "https://staging.app.rewards.network/api/partner/v1/qr/validate" \
  -H "Authorization: Bearer rn_live_..." \
  -H "Content-Type: application/json" \
  -d '{ "token": "CUST-LOYALTY-ID-RN-000123" }'
{
  "success": true,
  "data": {
    "qr_type": "customer_id",
    "customer_id": 123,
    "customer": {
      "user_id": 123,
      "full_name": "Jane Doe",
      "email": "jane@example.com",
      "is_member": true
    }
  }
}

qr_type tells you what was scanned:

qr_type Meaning Next call
customer_id The customer's identity QR (or their typed code RN-000123). transactions/commit
redemption A reward the customer wants to redeem (voucher / points / stamps). transactions/redeem — see Redeem a customer QR
network_reward A network reward QR (RWD-…). rewards/claim — see Rewards, packages, deals & gifts

If the customer has no phone, look them up by phone number instead: Customers & members.

Step 2: Preview

Tell the customer what they will earn before they pay.

curl -X POST "https://staging.app.rewards.network/api/partner/v1/transactions/preview" \
  -H "Authorization: Bearer rn_live_..." \
  -H "Content-Type: application/json" \
  -d '{ "amount": 48.50, "discount_amount": 5.00, "branch_id": 45 }'
{
  "success": true,
  "data": {
    "loyalty_enabled": true,
    "loyalty_running": true,
    "stamp_enabled": true,
    "stamp_running": true,
    "original_amount": 48.5,
    "discount_amount": 5,
    "final_amount": 43.5,
    "points_earned": 43,
    "stamps_earned": 1,
    "point_campaign_id": 17,
    "stamp_campaign_id": 18
  }
}

Points are computed on final_amount (amount minus discount). branch_id is optional but recommended: a campaign limited to certain branches only earns there, so a preview with the sale's branch shows exactly what commit will award. When the merchant has no running campaign (at that branch) the call answers success: false with data.reason (NO_ACTIVE_RULES, BRANCH_NOT_IN_CAMPAIGN, …) — treat it as "nothing to earn", not as an error.

Step 3: Commit

After payment succeeded:

curl -X POST "https://staging.app.rewards.network/api/partner/v1/transactions/commit" \
  -H "Authorization: Bearer rn_live_..." \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 7f6a2c1e-9d4b-4c3a-8e5f-0a1b2c3d4e5f" \
  -d '{
    "user_id": 123,
    "branch_id": 45,
    "amount": 48.50,
    "discount_amount": 5.00,
    "qr_scan_id": "POS-0001-20260922-0042"
  }'
Field Required Notes
user_id yes From qr/validate (customer_id) or customers/lookup. Must already be a member (is_member: true), otherwise success: false with data.reason = NOT_A_MEMBER — see Customers & members.
branch_id yes One of the merchant's branches (GET /branches). Campaigns limited to other branches award nothing here (BRANCH_NOT_IN_CAMPAIGN).
amount yes Gross amount, > 0.
discount_amount no Subtracted before points are computed.
redemption_id no A voucher/points/stamps redemption already applied to this sale. See below.
qr_scan_id recommended Your own stable reference for this sale (receipt number).
{
  "success": true,
  "message": "Transaction committed successfully",
  "data": {
    "transaction": {
      "id": 9901,
      "merchant_id": 7,
      "user_id": 123,
      "branch_id": 45,
      "qr_scan_id": "POS-0001-20260922-0042",
      "transaction_type": "earn",
      "original_amount": "48.50",
      "discount_amount": "5.00",
      "final_amount": "43.50",
      "points_delta": 43,
      "stamps_delta": 1,
      "source": "partner",
      "created_at": "2026-09-22T10:41:07.000000Z"
    },
    "points_earned": 43,
    "stamps_earned": 1,
    "loyalty_enabled": true,
    "stamp_enabled": true
  },
  "is_duplicate": false
}

Keep data.transaction.id: it is the transactionId for a void.

Retries and duplicates

Two independent safety nets, use both:

  • Idempotency-Key header — any unique string per attempt group (a UUID). A retry with the same key and the same body returns the stored response with header Idempotency-Replayed: true. Same key with a different body → 422. A retry while the first attempt is still running → 409. Errors are stored too: after a 4xx such as "Select the branch…", send the corrected request with a new key. Keys are remembered for 24 hours and are private to your merchant.
  • qr_scan_id body field — your receipt reference. If a transaction with this id already exists for the merchant, commit returns it with "is_duplicate": true instead of awarding again. This survives longer than the idempotency window. The id is unique per merchant and names one transaction: give the redeem of the same sale its own id (e.g. POS-0001-20260922-0042-R). An id that already belongs to another transaction (a redemption, or another customer's sale) answers success: false with data.reason: "QR_SCAN_ID_REUSED" and awards nothing.

Network timeouts on the POS side should always be retried with the same key and body. Never generate a new qr_scan_id for a retry.

Applying a voucher to the same sale

When the customer redeems a voucher and earns on the remaining amount, do the redemption first, then commit with the redemption_id:

  1. POST /qr/validate with the voucher QR → qr_type: "redemption", note redemption_id and the discount in metadata.
  2. Charge the customer the bill minus that discount (the server applies the same rule).
  3. POST /transactions/redeem with redemption_id, branch_id and amount = the bill before the discount — the server takes the discount off, completes the voucher and awards points on the amount paid. See Redeem a customer QR.

Only call transactions/commit with redemption_id when your flow completes the redemption separately and you need to link the earn to it.

Webhook

A successful commit fires transaction.committed to every endpoint subscribed to the merchant. Webhooks.