Webhook Events
Receive real-time notifications about transaction events via webhooks. Essential for keeping your systems in sync.
Setting Up Webhooks
- Go to your Dashboard
- Navigate to Settings → Webhooks
- Add your endpoint URL (must be HTTPS)
- Select the events you want to receive
- Copy your webhook signing secret
Event Types
| Event | Description |
|---|---|
| onramp.session.created | New onramp session initialized |
| onramp.session.processing | Payment received, crypto transfer started |
| onramp.session.completed | Crypto delivered to wallet successfully |
| onramp.session.failed | Payment or crypto delivery failed |
| onramp.session.expired | Session expired without payment |
Webhook Payload
All webhook events follow this structure:
{
"id": "evt_abc123xyz",
"type": "onramp.session.completed",
"created_at": "2024-01-15T12:08:00Z",
"data": {
"session_id": "onramp_sess_xyz789",
"transaction_id": "tx_def456",
"status": "completed",
"wallet_address": "0x742d35Cc6634C0532925a3b844Bc9e7595f5fB",
"crypto_currency": "ETH",
"crypto_amount": "0.0412",
"fiat_currency": "USD",
"fiat_amount": 10000,
"network": "ethereum",
"tx_hash": "0x1234567890abcdef...",
"metadata": {
"order_id": "ord_12345",
"user_id": "usr_67890"
}
}
}Event Examples
onramp.session.completed
Fired when crypto is successfully delivered to the wallet.
{
"id": "evt_completed_123",
"type": "onramp.session.completed",
"created_at": "2024-01-15T12:08:00Z",
"data": {
"session_id": "onramp_sess_xyz789",
"status": "completed",
"wallet_address": "0x742d35Cc6634C0532925a3b844Bc9e7595f5fB",
"crypto_currency": "ETH",
"crypto_amount": "0.0412",
"tx_hash": "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
"metadata": {
"order_id": "ord_12345"
}
}
}onramp.session.failed
Fired when payment fails or crypto delivery fails.
{
"id": "evt_failed_456",
"type": "onramp.session.failed",
"created_at": "2024-01-15T12:08:00Z",
"data": {
"session_id": "onramp_sess_abc123",
"status": "failed",
"failure_reason": "payment_declined",
"failure_message": "Card was declined by the issuing bank",
"metadata": {
"order_id": "ord_67890"
}
}
}Handling Webhooks
Example webhook handler in Node.js/Express:
1400">const express = require(400">039;express039;);2400">const crypto = require(400">039;crypto039;);3 4400">const app = express();5 6500">// Use raw body for signature verification7app.post(400">039;/webhooks/web3pay039;,8 express.raw({ 400">type: 400">039;application/json039; }),9 400">async (req, res) => {10 400">const signature = req.headers[400">039;x-web3pay-signature039;];11 400">const payload = req.body.toString();12 13 500">// Verify signature14 400">if (!verifySignature(payload, signature)) {15 console.error(400">039;Invalid webhook signature039;);16 400">return res.status(400).send(400">039;Invalid signature039;);17 }18 19 400">const event = JSON.parse(payload);20 21 500">// Handle event types22 400">switch (event.400">type) {23 400">case 400">039;onramp.session.completed039;:24 400">await handleCompleted(event.data);25 400">break;26 27 400">case 400">039;onramp.session.failed039;:28 400">await handleFailed(event.data);29 400">break;30 31 400">case 400">039;onramp.session.processing039;:32 400">await handleProcessing(event.data);33 400">break;34 35 400">default:36 console.log(400">039;Unhandled event 400">type:039;, event.400">type);37 }38 39 500">// Acknowledge receipt40 res.json({ received: 400">true });41 }42);43 44400">async 400">function handleCompleted(data) {45 400">const { session_id, tx_hash, metadata } = data;46 47 500">// Update your database48 400">await db.orders.update({49 where: { id: metadata.order_id },50 data: {51 status: 400">039;paid039;,52 txHash: tx_hash,53 completedAt: 400">new Date()54 }55 });56 57 500">// Notify the user58 400">await sendEmail(metadata.user_email, {59 subject: 400">039;Your crypto purchase is complete!039;,60 body: 400">`Transaction hash: ${tx_hash}`61 });62}63 64400">async 400">function handleFailed(data) {65 400">const { session_id, failure_reason, metadata } = data;66 67 400">await db.orders.update({68 where: { id: metadata.order_id },69 data: {70 status: 400">039;failed039;,71 failureReason: failure_reason72 }73 });74}75 76app.listen(3000);Signature Verification
Always verify webhook signatures to ensure authenticity:
400">const crypto = require(400">039;crypto039;);
400">const WEBHOOK_SECRET = process.env.WEB3PAY_WEBHOOK_SECRET;
400">function verifySignature(payload, signature) {
400">if (!signature) 400">return 400">false;
500">// Parse signature header: t=timestamp,v1=hash
400">const parts = signature.split(400">039;,039;);
400">const timestamp = parts[0].split(400">039;=039;)[1];
400">const hash = parts[1].split(400">039;=039;)[1];
500">// Check timestamp (reject 400">if older than 5 minutes)
400">const now = Math.floor(Date.now() / 1000);
400">if (now - parseInt(timestamp) > 300) {
console.error(400">039;Webhook timestamp too old039;);
400">return 400">false;
}
500">// Compute expected signature
400">const expected = crypto
.createHmac(400">039;sha256039;, WEBHOOK_SECRET)
.update(400">`${timestamp}.${payload}`)
.digest(400">039;hex039;);
500">// Use timing-safe comparison
400">return crypto.timingSafeEqual(
Buffer.400">from(hash),
Buffer.400">from(expected)
);
}Best Practices
Return 2xx quickly
Respond with 200 immediately, then process async. We'll retry if we don't receive a response within 30 seconds.
Handle duplicates
We may send the same event multiple times. Use the event ID to deduplicate and make your handlers idempotent.
Verify signatures
Always verify the webhook signature before processing. This prevents attackers from spoofing events.
Log everything
Log all incoming webhooks for debugging. Store the raw payload in case you need to reprocess later.
Retry Policy
If your endpoint returns a non-2xx status or times out, we'll retry:
| Attempt | Delay |
|---|---|
| 1st retry | 1 minute |
| 2nd retry | 5 minutes |
| 3rd retry | 30 minutes |
| 4th retry | 2 hours |
| 5th retry (final) | 24 hours |
After 5 failed attempts, the webhook is marked as failed. You can manually retry failed webhooks from the dashboard.