Webhooks
Receive real-time notifications when checkout events occur. Webhooks are the primary way to know when a payment is confirmed.
Setup
Configure your webhook URL in the Volr Dashboard under Project Settings > Checkout Config, or via CLI:
volr checkout setup
You'll receive a webhook secret (whsec_...) for signature verification.
Handling Webhooks
import { VolrCheckout, type WebhookPayload } from '@volr/checkout-sdk';
app.post('/webhooks/volr', express.raw({ type: 'application/json' }), async (req, res) => {
const signature = req.headers['x-volr-signature'] as string;
const event = req.headers['x-volr-event'] as string;
const payload = req.body.toString();
// 1. Verify signature
const isValid = await VolrCheckout.verifySignature(
payload,
signature,
process.env.VOLR_WEBHOOK_SECRET!,
);
if (!isValid) {
return res.status(401).send('Invalid signature');
}
// 2. Parse and handle event
const webhookEvent: WebhookPayload = JSON.parse(payload);
switch (webhookEvent.event) {
case 'checkout.paid':
await handlePaymentConfirmed(webhookEvent.data);
break;
case 'checkout.expired':
await handleCheckoutExpired(webhookEvent.data);
break;
case 'checkout.cancelled':
await handleCheckoutCancelled(webhookEvent.data);
break;
case 'checkout.settled':
await handleSettlement(webhookEvent.data);
break;
case 'checkout.late_paid':
await handleLatePaid(webhookEvent.data);
break;
}
// 3. Return 200 quickly
res.status(200).send('OK');
});
caution
Always verify the webhook signature before processing. Never trust unverified webhook payloads.
Webhook Events
| Event | Description | When |
|---|---|---|
checkout.paid | Payment confirmed on-chain | Customer payment is verified |
checkout.expired | Checkout expired | No payment received before expiry |
checkout.cancelled | Checkout was cancelled | Merchant cancelled the checkout |
checkout.settled | Funds settled to merchant | Payment fully settled |
checkout.late_paid | Late payment received | Payment arrived after expiry |
Webhook Payload
interface WebhookPayload {
event: string;
data: {
checkoutId: string;
projectId: string;
status: string;
referenceId: string | null;
chainId: number;
tokenAddress: string;
amount: string;
depositAddress: string;
fiatAmount: string | null;
fiatCurrency: string | null;
itemName: string | null;
customerEmail: string | null;
customerName: string | null;
paymentTxHash: string | null;
paidAmount: string | null;
paidAt: string | null;
createdAt: string;
expiresAt: string;
metadata: Record<string, unknown> | null;
};
timestamp: string;
}
Signature Verification
Webhooks are signed with HMAC-SHA256. The signature is sent in the X-Volr-Signature header.
// Using the SDK (recommended)
const isValid = await VolrCheckout.verifySignature(payload, signature, secret);
// Manual verification (any language)
import { createHmac } from 'crypto';
function verifyWebhook(payload: string, signature: string, secret: string): boolean {
const expected = createHmac('sha256', secret)
.update(payload)
.digest('hex');
return expected === signature;
}
Retry Policy
If your webhook endpoint returns a non-2xx response or times out, Volr retries with exponential backoff:
| Attempt | Delay |
|---|---|
| 1st | Immediate |
| 2nd | 1 minute |
| 3rd | 5 minutes |
| 4th | 15 minutes |
Each attempt has a 5-second timeout.
Best Practices
- Return 200 quickly — Process the event asynchronously after acknowledging receipt
- Handle duplicates — Use
checkoutIdas an idempotency key; you may receive the same event multiple times - Verify signatures — Always validate the
X-Volr-Signatureheader - Use referenceId — Map webhooks to your internal orders via
referenceId - Log everything — Store raw webhook payloads for debugging
Next Steps
- Refunds — Process refunds for paid checkouts
- Code Examples — Full integration examples