Vouchereto API · v1
Quickstart
Redeem your first voucher in the sandbox: get a test key, create a test voucher, reserve it, capture it and watch the webhook arrive. No real money is involved.
1. Get a test API key
- Sign in to the merchant portal at merchant.vouchereto.com (accounts are invite-only: request access).
- Switch the portal to Test mode (Modo prueba) and open Developers (Desarrolladores) → API keys.
- Create a key. It looks like
vch_test_AbC123dEf456_…and is shown once: store it in your secret manager.
Export it in your shell so the samples below can read it:
export VOUCHERETO_API_KEY="vch_test_…"
2. Create a test voucher
Sandbox vouchers are real codes tied to your merchant, created in the state you ask for. Every request must be signed (the samples show how; the signing page explains it).
# Bash + openssl: sign and send a request
KEY="$VOUCHERETO_API_KEY" # vch_test_…
API="${VOUCHERETO_API:-https://api.vouchereto.com}"
SECRET="${KEY##*_}" # the secret is the last "_" segment of the key
METHOD="POST"
PATH_Q="/v1/sandbox/vouchers" # path (with /v1 and any ?query)
BODY='{"state":"active","amount_minor":5000,"currency":"USD"}'
T=$(date +%s)
SIG=$(printf '%s.%s %s\n%s' "$T" "$METHOD" "$PATH_Q" "$BODY" \
| openssl dgst -sha256 -hmac "$SECRET" -hex | sed 's/^.* //')
curl -sS -X "$METHOD" "$API$PATH_Q" \
-H "Authorization: Bearer $KEY" \
-H "Vouchereto-Signature: t=$T,v1=$SIG" \
-H "Idempotency-Key: $(openssl rand -hex 16)" \
-H "Content-Type: application/json" \
--data-raw "$BODY"
// Node 18+, no dependencies
import { createHmac, randomUUID } from 'node:crypto';
const API = process.env.VOUCHERETO_API ?? 'https://api.vouchereto.com';
const KEY = process.env.VOUCHERETO_API_KEY; // vch_test_…
const SECRET = KEY.split('_').at(-1); // last "_" segment
async function vouchereto(method, path, body) {
const raw = body === undefined ? '' : JSON.stringify(body);
const t = Math.floor(Date.now() / 1000);
const mac = createHmac('sha256', SECRET).update(`${t}.${method} ${path}\n${raw}`).digest('hex');
const headers = { Authorization: `Bearer ${KEY}`, 'Vouchereto-Signature': `t=${t},v1=${mac}` };
if (method === 'POST') Object.assign(headers, { 'Content-Type': 'application/json', 'Idempotency-Key': randomUUID() });
const res = await fetch(API + path, { method, headers, body: raw || undefined });
const json = await res.json();
if (!res.ok) throw new Error(`${json.code} ${json.detail ?? ''} (request ${json.request_id})`);
return json;
}
console.log(await vouchereto('POST', '/v1/sandbox/vouchers', { state: 'active', amount_minor: 5000, currency: 'USD' }));
# Python 3.9+ with requests
import hashlib, hmac, json, os, time, uuid
import requests
API = os.environ.get("VOUCHERETO_API", "https://api.vouchereto.com")
KEY = os.environ["VOUCHERETO_API_KEY"] # vch_test_…
SECRET = KEY.rsplit("_", 1)[1] # last "_" segment
def vouchereto(method, path, body=None):
raw = "" if body is None else json.dumps(body, separators=(",", ":"))
t = str(int(time.time()))
mac = hmac.new(SECRET.encode(), f"{t}.{method} {path}\n{raw}".encode(), hashlib.sha256).hexdigest()
headers = {"Authorization": f"Bearer {KEY}", "Vouchereto-Signature": f"t={t},v1={mac}"}
if method == "POST":
headers.update({"Content-Type": "application/json", "Idempotency-Key": str(uuid.uuid4())})
# send exactly the bytes you signed: data=raw, not json=body
r = requests.request(method, API + path, headers=headers, data=raw.encode() if raw else None, timeout=15)
out = r.json()
if r.status_code >= 400:
raise RuntimeError(f"{out['code']} {out.get('detail', '')} (request {out.get('request_id')})")
return out
print(vouchereto("POST", "/v1/sandbox/vouchers", {"state": "active", "amount_minor": 5000, "currency": "USD"}))
<?php
// PHP 8 with ext-curl
function vouchereto(string $method, string $path, ?array $body = null): array {
$api = getenv('VOUCHERETO_API') ?: 'https://api.vouchereto.com';
$key = getenv('VOUCHERETO_API_KEY'); // vch_test_…
$secret = substr($key, strrpos($key, '_') + 1); // last "_" segment
$raw = $body === null ? '' : json_encode($body, JSON_UNESCAPED_SLASHES);
$t = time();
$sig = hash_hmac('sha256', "$t.$method $path\n$raw", $secret);
$headers = ["Authorization: Bearer $key", "Vouchereto-Signature: t=$t,v1=$sig"];
if ($method === 'POST') {
$headers[] = 'Content-Type: application/json';
$headers[] = 'Idempotency-Key: ' . bin2hex(random_bytes(16));
}
$ch = curl_init($api . $path);
curl_setopt_array($ch, [CURLOPT_CUSTOMREQUEST => $method, CURLOPT_HTTPHEADER => $headers, CURLOPT_RETURNTRANSFER => true]);
if ($raw !== '') curl_setopt($ch, CURLOPT_POSTFIELDS, $raw);
$res = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);
$json = json_decode($res, true);
if ($status >= 400) throw new RuntimeException("{$json['code']} (request {$json['request_id']})");
return $json;
}
print_r(vouchereto('POST', '/v1/sandbox/vouchers', ['state' => 'active', 'amount_minor' => 5000, 'currency' => 'USD']));
The response contains code, for example K7QM-4XPD-9TRW-2WNZ. That is what a customer would type on your site.
3. Reserve the voucher
Reserving locks the voucher's full value for ttl_seconds (default 900). Use the same helper with a different path and body:
POST /v1/redemptions
{ "code": "K7QM-4XPD-9TRW-2WNZ", "currency": "USD", "customer_ref": "user_8842" }
201 Created
{
"id": "f4e8b730-294e-4351-a85d-dc525dcadc18",
"object": "redemption",
"status": "reserved",
"amount_minor": 5000,
"currency": "USD",
"voucher_last4": "2WNZ",
"customer_ref": "user_8842",
"livemode": false,
"expires_at": "2026-10-22T17:37:55.915Z",
"captured_at": null,
"created_at": "2026-10-22T17:22:55.915Z"
}
4. Capture it
Capture once your side succeeded (for example, after you added the balance to the customer's account). Captures are final.
POST /v1/redemptions/f4e8b730-294e-4351-a85d-dc525dcadc18/capture
(empty body, still signed, with its own Idempotency-Key)
200 OK
{ "id": "f4e8b730-…", "object": "redemption", "status": "captured", "captured_at": "2026-10-22T17:22:56.118Z", … }
If something went wrong on your side, call POST /v1/redemptions/{id}/cancel instead and the customer keeps the voucher.
5. Receive the webhook
- In the portal (test mode) open Developers → Webhooks and add an
https://endpoint subscribed toredemption.captured. Copy itswhsec_…secret. - Capture another sandbox voucher: within a minute your endpoint receives a signed
redemption.capturedevent. - Verify the signature before trusting it: code in Node, Python and PHP.
cloudflared tunnel --url http://localhost:8080) or a request bin while you develop. The portal shows every delivery and lets you resend it.Do it all with one command
The same flow, plus idempotent retries, the single-use check, listing, cancelling and creating checkout and redeem sessions, is in our repository as a script you can run against the sandbox:
VOUCHERETO_API_KEY=vch_test_… npx tsx scripts/sandbox/quickstart.ts
Next: choose your integration in the guides, then read idempotency, errors and limits before going live.