Skip to content

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.

Terminal window
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.
EventTrigger
voucher.redeemedA redemption completed successfully
voucher.validatedA validation check was performed
voucher.expiredA voucher passed its expiry date
campaign.startedCampaign start time reached
campaign.endedCampaign end time reached
campaign.budget_reachedmax_redemptions limit hit
redemption.rolled_backA redemption was reversed
fraud.alertVelocity anomaly detected
{
"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
}
}

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));
}

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.