Docs/Webhooks

Webhook Events

Receive real-time notifications about transaction events via webhooks. Essential for keeping your systems in sync.

Setting Up Webhooks

  1. Go to your Dashboard
  2. Navigate to Settings → Webhooks
  3. Add your endpoint URL (must be HTTPS)
  4. Select the events you want to receive
  5. Copy your webhook signing secret

Event Types

EventDescription
onramp.session.createdNew onramp session initialized
onramp.session.processingPayment received, crypto transfer started
onramp.session.completedCrypto delivered to wallet successfully
onramp.session.failedPayment or crypto delivery failed
onramp.session.expiredSession expired without payment

Webhook Payload

All webhook events follow this structure:

json
{
  "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.

json
{
  "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.

json
{
  "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:

javascript
1400">const express = require(400">'express');
2400">const crypto = require(400">'crypto');
3 
4400">const app = express();
5 
6500">// Use raw body for signature verification
7app.post(400">'/webhooks/web3pay',
8 express.raw({ 400">type: 400">'application/json' }),
9 400">async (req, res) => {
10 400">const signature = req.headers[400">'x-web3pay-signature'];
11 400">const payload = req.body.toString();
12 
13 500">// Verify signature
14 400">if (!verifySignature(payload, signature)) {
15 console.error(400">'Invalid webhook signature');
16 400">return res.status(400).send(400">'Invalid signature');
17 }
18 
19 400">const event = JSON.parse(payload);
20 
21 500">// Handle event types
22 400">switch (event.400">type) {
23 400">case 400">'onramp.session.completed':
24 400">await handleCompleted(event.data);
25 400">break;
26 
27 400">case 400">'onramp.session.failed':
28 400">await handleFailed(event.data);
29 400">break;
30 
31 400">case 400">'onramp.session.processing':
32 400">await handleProcessing(event.data);
33 400">break;
34 
35 400">default:
36 console.log(400">'Unhandled event 400">type:', event.400">type);
37 }
38 
39 500">// Acknowledge receipt
40 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 database
48 400">await db.orders.update({
49 where: { id: metadata.order_id },
50 data: {
51 status: 400">'paid',
52 txHash: tx_hash,
53 completedAt: 400">new Date()
54 }
55 });
56 
57 500">// Notify the user
58 400">await sendEmail(metadata.user_email, {
59 subject: 400">'Your crypto purchase is complete!',
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">'failed',
71 failureReason: failure_reason
72 }
73 });
74}
75 
76app.listen(3000);

Signature Verification

Always verify webhook signatures to ensure authenticity:

javascript
400">const crypto = require(400">'crypto');

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">',');
  400">const timestamp = parts[0].split(400">'=')[1];
  400">const hash = parts[1].split(400">'=')[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">'Webhook timestamp too old');
    400">return 400">false;
  }

  500">// Compute expected signature
  400">const expected = crypto
    .createHmac(400">'sha256', WEBHOOK_SECRET)
    .update(400">`${timestamp}.${payload}`)
    .digest(400">'hex');

  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:

AttemptDelay
1st retry1 minute
2nd retry5 minutes
3rd retry30 minutes
4th retry2 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.

Back to home
Was this page helpful?