Skip to content

Your first redemption

  1. Get your keys

    Create a test key in the merchant portal. You see the full key once; its last part is also the secret you sign requests with.

  2. Reserve the voucher

    Send the code, the currency and your customer reference. The voucher is locked to that customer and cannot be used anywhere else.

  3. Credit, then capture

    Credit the balance on your side, then capture. If the top-up fails, cancel instead and the voucher is released.

  4. Listen to webhooks

    Register an endpoint to receive signed events for captures, cancellations, expiries and checkouts.

Redemptions · example
POST /v1/redemptions
Authorization: Bearer vch_test_…
Idempotency-Key: topup-88213
Vouchereto-Signature: t=1790500000,v1=9c1e5f07…
Content-Type: application/json

{ "code": "7K2M-9QXR-4HTB-2WNZ", "customer_ref": "acct_48213", "currency": "USD" }

# 201 Created
{ "id": "5f0c2a7e-…", "object": "redemption", "status": "reserved",
  "amount_minor": 5000, "currency": "USD", "voucher_last4": "2WNZ",
  "customer_ref": "acct_48213", "livemode": false,
  "expires_at": "2026-09-27T14:12:00Z", "captured_at": null }

The redemption lifecycle

active voucherreserved
  • capturedThe voucher is spent. Irreversible.
  • cancelledYou cancelled it. The voucher is active again.
  • reservation expiredNot captured in time (15 minutes by default). The voucher is active again.

What to build against

Signing requests

Every request carries a Vouchereto-Signature header: a timestamp and an HMAC-SHA256 of the timestamp, the method, the path and the body, keyed with your API key secret. We accept a clock skew of up to 300 seconds.

sign.js
// Sign every request with the secret part of your API key (the text after the last "_")
const t = Math.floor(Date.now() / 1000);
const payload = `${t}.${method} ${pathWithQuery}\n${body}`;
const v1 = crypto.createHmac("sha256", keySecret).update(payload).digest("hex");
headers["Vouchereto-Signature"] = `t=${t},v1=${v1}`;

Idempotency

Every POST needs an Idempotency-Key header. Repeat a request with the same key and you get the original response back, marked Idempotent-Replayed: true. Keys are kept for at least 30 days.

Sandbox vouchers

With a test key, POST /v1/sandbox/vouchers creates a test voucher in the state you ask for (active, reserved, redeemed, expired, risk_hold or void) and returns its code. Test keys only ever see test data.

Webhook events

Events we send to your endpoint
EventWhen it is sent
redemption.capturedA reserved voucher was captured. Safe to keep the credited balance.
redemption.cancelledA reservation was cancelled. The voucher is active again.
redemption.expiredA reservation ran out of time without a capture.
checkout.completedA hosted checkout was paid and the voucher applied.
checkout.expiredA hosted checkout session expired without payment.
checkout.failedA paid checkout was reversed, for example after a refund.

Errors

Error codes and what to tell the customer
StatusCodeWhat to tell the customer
404VOUCHER_INVALIDWe could not find that code. Check it and try again.
409VOUCHER_ALREADY_REDEEMEDThis voucher has already been used.
409VOUCHER_RESERVEDThis voucher is being used in another payment right now.
422VOUCHER_EXPIREDThis voucher has expired.
422UNIVERSAL_NOT_ACCEPTEDWe do not accept universal vouchers. Use a voucher for this site.
422CURRENCY_MISMATCHThis voucher is in a different currency.
429TOO_MANY_INVALID_ATTEMPTSToo many attempts. Wait a few minutes and try again.

Errors follow RFC 7807 (application/problem+json) with a stable code and a request_id.

This page summarises the API. The full reference with every field is published at www.vouchereto.com/docs/reference.

Stuck on something?

Write to [DEVELOPER SUPPORT EMAIL]. A developer reads every message.