The system of record for vouchers

Whether a voucher exists, which channel owns it, and who redeemed it for how much: UVS is the authority.

POST /v1/redemptions/lock
x-uvs-access-key:  ak_live_7f3c1a2e
x-uvs-timestamp:   1785072041
x-uvs-nonce:       9d41b0
x-uvs-signature:   HMAC-SHA256(...)
idempotency-key:   req_lock_001

{
  "request_id":   "req_lock_001",
  "coupon":       "164TGDA8HAQ6XS1K",
  "merchant":     "mch_abc123",
  "order_amount": 100,
  "currency":     "CNY"
}

200 OK
{
  "ok":              true,
  "redemption":      "red_lock_998877",
  "discount_amount": 15.5
}

Boundaries matter more than capabilities

UVS owns the full lifecycle of the voucher itself. Money, delivery and payment each live in their own systems. Wherever that boundary stays vague, it shows up later as the rows that will not match at reconciliation time.

Inside UVS

  • Voucher templates, batch issuing, code security, stock
  • Allocation, quota transfer and channel attribution
  • Redemption: two-phase, idempotent, concurrency-guarded, reversible
  • Risk control: anomaly detection, anti-forgery, anti-enumeration
  • Ledger, reconciliation files and outbound event callbacks

Outside UVS

  • Settlement and revenue split. UVS never touches funds
  • Voucher delivery: SMS, QR codes, claim pages
  • Payment collection, run by the merchant's own stack
  • Merchant onboarding review and approval

Allocation is inside UVS. Delivery is outside. Handing ten thousand vouchers to channel A, deducting its quota and permanently stamping ownership is allocation, and reconciliation depends on it. How channel A then reaches whom, UVS does not know and does not need to.

Status is not a field you update by hand

A voucher's status is the materialized result of its event ledger. Every transition is a guarded conditional update, so under concurrency it either succeeds exactly once or fails. Two requests never redeem the same voucher.

CREATED ISSUED LOCKED REDEEMED
Other terminal states REVERSED EXPIRED VOIDED

Three kinds of integrator, one contract

Authentication by Party, authorization by role. Under a single contract, makers, distributors and acceptance sides each get the endpoints their role covers.

Merchant

POS and online checkout start the redemption. Lock first, confirm second, with cancel and reverse for the exception paths.

  • POST /v1/redemptions/lock
  • POST /v1/redemptions/confirm
  • POST /v1/redemptions/cancel
  • POST /v1/redemptions/redeem
  • POST /v1/redemptions/reverse
  • POST /v1/redemptions/void

Maker

Submit a batch, track its progress, release batch quota to a channel.

  • POST /v1/batches
  • GET /v1/batches/{batchId}
  • POST /v1/batches/allocate

Distributor

Pull events incrementally to compensate, fetch daily statements and discrepancy lists.

  • GET /v1/events
  • GET /v1/reconciliations
  • GET /v1/reconciliations/{date}/{kind}

Three guarantees you build on

Every write is idempotent
Write endpoints require an idempotency key. A retry carrying the same request_id replays the first response instead of executing again, and does not error. That is what keeps a checkout from redeeming twice when the network drops.
Callbacks are at-least-once
Lifecycle events go out through a transactional outbox, signed with HMAC, guaranteed to arrive at least once. Subscribers deduplicate by event ID. Anything missed can be recovered through GET /v1/events.
The ledger is append-only
State changes land in the event ledger first. The status column on the business table is a materialized view of that ledger, and the current state can be rebuilt from it at any time. This is the precondition for reconciliation to balance.

Ready to integrate

Tell us which kind of integrator you are, plus the voucher types and volume you expect. We will send back an AppKey and everything the integration needs.

contact@uvs-online.com