Skip to main content

Overview

Webhooks allow you to receive real-time notifications when settlement status changes, eliminating the need for polling.

Setup

Provide a webhook_url when verifying a payment:
{
  "transaction_hash": "0x...",
  "chain": "base",
  "seller_address": "0x...",
  "expected_amount": "1000000000000000000",
  "webhook_url": "https://your-app.com/webhooks/settlement"
}

Webhook Payload

When settlement completes, we’ll POST to your webhook URL:
{
  "event_type": "settlement.completed",
  "event_id": "evt_abc123def456",
  "timestamp": "2025-01-15T10:35:00Z",
  "verification_id": "ver_abc123def456",
  "settlement_id": "set_xyz789ghi012",
  "status": "settled",
  "data": {
    "settlement_tx_hash": "0xabcdef...",
    "settled_amount": "1000000000000000000",
    "fee": "50000000000000000"
  },
  "signature": "hmac_sha256_signature_here"
}

Event Types

verification.completed
event
Payment verification completed successfully
verification.failed
event
Payment verification failed
settlement.completed
event
Settlement completed successfully
settlement.rejected
event
Settlement was rejected
status.updated
event
General status update

Signature Verification

CRITICAL: Always verify webhook signatures to ensure requests are from 0xmeta.ai
1

Extract Signature

Get the signature from the X-Webhook-Signature header
2

Compute Expected Signature

Use HMAC-SHA256 with your webhook secret
3

Compare Signatures

Use constant-time comparison to prevent timing attacks
const crypto = require("crypto");

function verifyWebhook(payload, signature, secret) {
  // Serialize payload to canonical JSON
  const payloadStr = JSON.stringify(payload, Object.keys(payload).sort(), ":");

  // Compute expected signature
  const expected = crypto
    .createHmac("sha256", secret)
    .update(payloadStr)
    .digest("hex");

  // Constant-time comparison
  return crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected));
}

// In your webhook handler
app.post("/webhooks/settlement", (req, res) => {
  const signature = req.headers["x-webhook-signature"];
  const payload = req.body;

  if (!verifyWebhook(payload, signature, process.env.WEBHOOK_SECRET)) {
    return res.status(401).json({ error: "Invalid signature" });
  }

  // Process webhook
  console.log("Settlement completed:", payload.settlement_id);

  res.json({ received: true });
});

Retry Logic

If your webhook endpoint fails, we’ll retry with exponential backoff:
AttemptDelay
11 minute
25 minutes
315 minutes
430 minutes
51 hour
After 5 failed attempts, the webhook is marked as failed.

Best Practices

  • Acknowledge receipt immediately
  • Process webhook asynchronously
  • Don’t perform long operations in webhook handler
app.post('/webhooks', async (req, res) => {
  // Verify signature
  if (!verifyWebhook(...)) {
    return res.status(401).end();
  }
  
  // Acknowledge immediately
  res.status(200).json({ received: true });
  
  // Process asynchronously
  processWebhook(req.body).catch(console.error);
});
Store event IDs to prevent duplicate processing:
const processedEvents = new Set();

app.post('/webhooks', (req, res) => {
  const eventId = req.body.event_id;
  
  if (processedEvents.has(eventId)) {
    return res.status(200).json({ received: true });
  }
  
  // Process webhook
  processWebhook(req.body);
  processedEvents.add(eventId);
  
  res.status(200).json({ received: true });
});
  • Webhook URLs must use HTTPS in production - Use valid SSL certificates - Don’t use self-signed certificates
  • Log all webhook deliveries
  • Alert on failed deliveries
  • Monitor response times
  • Track processing errors

Testing Webhooks Locally

Use ngrok to expose your local server:
# Start ngrok
ngrok http 3000

# Use the ngrok URL in your API request
{
  "webhook_url": "https://abc123.ngrok.io/webhooks/settlement"
}
Or use webhook.site to inspect payloads:
# Get a unique URL
https://webhook.site/your-unique-id

# Use it in your request
{
  "webhook_url": "https://webhook.site/your-unique-id"
}

Webhook Flow Diagram

Never expose your webhook secret in client-side code or version control.
Webhooks timeout after 10 seconds. Ensure your endpoint responds quickly.