Vouchereto API · v1
SDK & tools
Everything you need is downloadable from this site. Use the SDK for Node, or any HTTP client with the signing helper from the authentication page.
Downloads
| File | What it is |
|---|---|
| vouchereto-node.ts | Node 18+ SDK in one TypeScript file, no dependencies: typed client, request signing, webhook verification |
| openapi.yaml | OpenAPI 3.1 description of the whole API (also as JSON): generate clients, import into tools, validate |
| Postman collection | Every endpoint, with a pre-request script that signs requests and adds idempotency keys |
| Sandbox environment | Postman environment for https://api.vouchereto.com; paste your test key |
| Local environment | Postman environment for the local stack (http://localhost:3003) |
Node SDK
Copy vouchereto-node.ts into your project (we will publish it on npm as @vouchereto/node). It signs every request, adds an Idempotency-Key to POSTs when you do not pass one, and throws VoucheretoError with code, status, requestId and retryAfter.
usage
// Single-file SDK: download /docs/vouchereto-node.ts (TypeScript, Node 18+, no dependencies)
import { Vouchereto, VoucheretoError } from './vouchereto-node';
const v = new Vouchereto({ apiKey: process.env.VOUCHERETO_API_KEY! }); // baseUrl defaults to https://api.vouchereto.com/v1
const voucher = await v.sandbox.createVoucher({ amount_minor: 5000, currency: 'USD' });
const r = await v.redemptions.reserve(
{ code: voucher.code, currency: 'USD', customer_ref: 'user_8842' },
{ idempotencyKey: 'order-3141-reserve', customerIp: '203.0.113.9' },
);
await v.redemptions.capture(r.id, { idempotencyKey: 'order-3141-capture' });
try {
await v.redemptions.reserve({ code: voucher.code, currency: 'USD', customer_ref: 'user_8842' });
} catch (e) {
if (e instanceof VoucheretoError) console.log(e.code, e.requestId); // VOUCHER_ALREADY_REDEEMED req_…
}
API
| Method | Endpoint |
|---|---|
redemptions.reserve(body, opts) | POST /v1/redemptions |
redemptions.capture(id, opts) | POST /v1/redemptions/:id/capture |
redemptions.cancel(id, opts) | POST /v1/redemptions/:id/cancel |
redemptions.get(id) · list(query) | GET /v1/redemptions[/:id] |
checkoutSessions.create(body, opts) · get(id) | /v1/checkout-sessions |
redeemSessions.create(body, opts) | POST /v1/redeem-sessions |
sandbox.createVoucher(body, opts) | POST /v1/sandbox/vouchers |
settlements.list() | GET /v1/settlements |
verifyWebhook(raw, header, secrets) | Verify and parse a webhook (throws WebhookSignatureError) |
signRequest(…) · secretFromKey(key) | Low-level signing helpers |
opts is { idempotencyKey?, customerIp? }. Pass baseUrl: 'http://localhost:3003/v1' for the local stack.
Without the SDK
- Signing in curl, Node, Python and PHP: Authentication.
- Webhook verification in Node, Python and PHP: Webhooks.
webhook handler without the SDK (Node)
// Express: verify before parsing. The signature covers the RAW body.
import express from 'express';
import { createHmac, timingSafeEqual } from 'node:crypto';
function verifyWebhook(rawBody, header, secret, tolerance = 300) {
const parts = (header ?? '').split(',').map((p) => p.trim().split('='));
const t = Number(parts.find(([k]) => k === 't')?.[1]);
const sigs = parts.filter(([k]) => k === 'v1').map(([, v]) => Buffer.from(v ?? '', 'hex'));
if (!Number.isInteger(t) || Math.abs(Date.now() / 1000 - t) > tolerance) throw new Error('stale or malformed signature');
const expected = createHmac('sha256', secret).update(`${t}.${rawBody}`).digest();
if (!sigs.some((s) => s.length === expected.length && timingSafeEqual(s, expected))) throw new Error('bad signature');
return JSON.parse(rawBody);
}
const app = express();
app.post('/hooks/vouchereto', express.raw({ type: 'application/json' }), async (req, res) => {
let event;
try { event = verifyWebhook(req.body.toString('utf8'), req.get('Vouchereto-Signature'), process.env.VOUCHERETO_WEBHOOK_SECRET); }
catch { return res.sendStatus(400); }
// Deliveries can repeat: skip events you already processed (event.id === Vouchereto-Event-Id header)
// if (await seen(event.id)) return res.sendStatus(200);
if (event.type === 'redemption.captured') { /* credit event.data.customer_ref with event.data.amount_minor */ }
res.sendStatus(200); // answer within 10 s; do slow work in a queue
});
Scripts in our repository
| Script | Does |
|---|---|
scripts/sandbox/quickstart.ts | Runs the whole quickstart against a sandbox (smoke test of your key and network path) |
scripts/sandbox/seed-demo-merchant.ts | Local stack: creates a demo sandbox merchant, test key and webhook endpoint |
tests/docs/run-samples.ts | Executes every code sample of these docs against an API |
Other languages: the OpenAPI file works with openapi-generator for Java, C#, Go, Python, PHP and more. Generated clients do not sign requests on their own; add the signature as a request interceptor using the recipe on the authentication page.