Skip to content
Documentation menu

Vouchereto API · v1

Redeem codes with the API

Your customer types a voucher code in your own checkout or wallet page; your server reserves it, does its work, then captures or cancels.

The flow

two-step redemption
customer types code ──► POST /v1/redemptions            201 reserved   (voucher locked, nothing charged)
                         │
     your side succeeds ─┼─► POST /v1/redemptions/{id}/capture   200 captured  → credit the customer
     your side fails    ─┴─► POST /v1/redemptions/{id}/cancel    200 cancelled → voucher usable again
     you do nothing     ──►  after ttl_seconds                   expired       → voucher usable again

Two steps keep you safe: if crediting the customer fails on your side after the voucher was reserved, you cancel and the customer loses nothing.

Implementation

top-up.ts (Node SDK)
import { Vouchereto, VoucheretoError } from './vouchereto-node';
const v = new Vouchereto({ apiKey: process.env.VOUCHERETO_API_KEY! });

export async function topUpWithVoucher(user: { id: string }, code: string, customerIp: string, topUpId: string) {
  let r;
  try {
    r = await v.redemptions.reserve({ code, currency: 'USD', customer_ref: user.id, ttl_seconds: 300 },
                                    { idempotencyKey: `topup-${topUpId}-reserve`, customerIp });
  } catch (e) {
    if (e instanceof VoucheretoError) return { ok: false, reason: e.code };   // show a message per code
    throw e;
  }
  try {
    await wallet.credit(user.id, r.amount_minor, r.currency, { redemptionId: r.id });   // idempotent on r.id
  } catch (e) {
    await v.redemptions.cancel(r.id, { idempotencyKey: `topup-${topUpId}-cancel` });
    throw e;
  }
  await v.redemptions.capture(r.id, { idempotencyKey: `topup-${topUpId}-capture` });
  return { ok: true, amountMinor: r.amount_minor };
}
  1. Normalise nothing: send the code as typed. Dashes, spaces and lower case are handled by us.
  2. Send the customer's IP as X-Customer-Ip so invalid-code limits apply to that customer, not to your server.
  3. Choose ttl_seconds (60–1800) to cover your own processing time. Short is better.
  4. Capture only after your side committed. Crediting must be idempotent on the redemption id (a redemption.captured webhook may arrive too).

Messages for your customer

Customer-facing messages
codeSuggested message
VOUCHER_INVALIDWe could not find that code. Check it and try again.
VOUCHER_ALREADY_REDEEMEDThis voucher has already been used.
VOUCHER_EXPIREDThis voucher has expired.
VOUCHER_RESERVEDThis voucher is being used in another payment. Try again in a few minutes.
CURRENCY_MISMATCHThis voucher is in a different currency.
UNIVERSAL_NOT_ACCEPTEDThis type of voucher is not accepted here.
TOO_MANY_INVALID_ATTEMPTSToo many attempts. Please try again later.
Never echo the full code back to the page, a log or an analytics tool. The API returns only voucher_last4; use that in receipts.

Reconciliation

  • Store the redemption id with your own transaction.
  • Nightly, list GET /v1/redemptions?status=captured for the day and compare with your records; investigate differences with the request_ids in your logs.
  • Payouts arrive weekly with a CSV of every captured redemption: Settlements.