Webhook events
Vauchflow delivers events to your endpoint via HTTP POST using the transactional outbox pattern — events are guaranteed to be delivered at least once, even across API restarts.
Register an endpoint
Section titled “Register an endpoint”curl -X POST https://api.vauchflow.com/v1/webhooks \ -H "Authorization: Bearer vf_sk_YOUR_KEY" \ -H "Content-Type: application/json" \ -d '{ "url": "https://yourapp.com/webhooks/vauchflow", "events": ["voucher.redeemed", "campaign.budget_reached"] }'
# Response includes your signing secret — save it now, it won't be shown again.Event types
Section titled “Event types”| Event | Trigger |
|---|---|
voucher.redeemed | A redemption completed successfully |
voucher.validated | A validation check was performed |
voucher.expired | A voucher passed its expiry date |
campaign.started | Campaign start time reached |
campaign.ended | Campaign end time reached |
campaign.budget_reached | max_redemptions limit hit |
redemption.rolled_back | A redemption was reversed |
fraud.alert | Velocity anomaly detected |
Payload format
Section titled “Payload format”{ "id": "evt_01HX...", "type": "voucher.redeemed", "timestamp": "2026-04-17T14:30:00Z", "data": { "voucher_code": "SAVE20", "customer_id": "cust_xyz", "discount_applied": 18.00, "remaining_redemptions": 498 }}Verifying signatures
Section titled “Verifying signatures”Every payload is signed with HMAC-SHA256. Verify to prevent replay attacks:
const crypto = require('crypto');
function verifyWebhook(rawBody, signatureHeader, secret) { const [timestamp, signature] = signatureHeader.split(','); const ts = timestamp.replace('t=', ''); const sig = signature.replace('v1=', '');
const expected = crypto .createHmac('sha256', secret) .update(`${ts}.${rawBody}`) .digest('hex');
const diff = Math.abs(Date.now() / 1000 - Number(ts)); if (diff > 300) { throw new Error('Timestamp too old — possible replay attack'); }
return crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected));}Retry schedule
Section titled “Retry schedule”immediate → 5 min → 30 min → 2h → 5h → 10h → every 12h → 72h max
After 72 hours without successful delivery, the event moves to the dead-letter queue, visible in your dashboard for manual replay.