Skip to main content

Why Webhooks?

Webhooks are essential for production applications because:

No Polling

Get instant notifications instead of checking every few seconds

Efficient

Save bandwidth and reduce server load

Real-time

Know immediately when settlement completes

Reliable

Automatic retries if your endpoint is down

How Webhooks Work

Step 1: Provide Webhook URL

Include webhook_url when verifying payment:
const response = await fetch("https://facilitator.api.0xmeta.ai/v1/verify", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "Idempotency-Key": `verify_${orderId}`,
  },
  body: JSON.stringify({
    transaction_hash: txHash,
    chain: "base",
    seller_address: sellerAddress,
    expected_amount: amount,
    webhook_url: "https://your-app.com/webhooks/settlement", // Add this!
  }),
});

Step 2: Create Webhook Endpoint

import express from "express";
import crypto from "crypto";

const app = express();
app.use(express.json());

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

  // 1. Verify signature FIRST
  if (!verifySignature(payload, signature)) {
    console.error("Invalid webhook signature");
    return res.status(401).json({ error: "Invalid signature" });
  }

  // 2. Acknowledge receipt immediately
  res.status(200).json({ received: true });

  // 3. Process webhook asynchronously
  processWebhook(payload).catch(console.error);
});

function verifySignature(payload, signature) {
  // Get from environment
  const secret = process.env.WEBHOOK_SECRET;

  // Serialize payload (same way 0xmeta.ai does)
  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));
}

async function processWebhook(payload) {
  const { event_type, settlement_id, status, data } = payload;

  if (event_type === "settlement.completed" && status === "settled") {
    // Update order in database
    await db.orders.update({
      where: { settlement_id },
      data: {
        status: "completed",
        settlement_tx_hash: data.settlement_tx_hash,
        settled_at: new Date(),
      },
    });

    // Send confirmation email
    await sendConfirmationEmail(settlement_id);

    console.log("Settlement completed:", settlement_id);
  }
}

app.listen(3000);

Step 3: Test Locally with ngrok

# Install ngrok
npm install -g ngrok

# Start your server
node server.js  # Port 3000

# In another terminal, start ngrok
ngrok http 3000

# Output will show:
# Forwarding: https://abc123.ngrok.io -> http://localhost:3000

# Use the ngrok URL in your API request
{
  "webhook_url": "https://abc123.ngrok.io/webhooks/settlement"
}

Webhook Payload

When settlement completes, you’ll receive:
{
  "event_type": "settlement.completed",
  "event_id": "evt_abc123def456",
  "timestamp": "2025-01-15T10:35:00Z",
  "verification_id": "ver_abc123",
  "settlement_id": "set_xyz789",
  "status": "settled",
  "data": {
    "settlement_tx_hash": "0xabcdef...",
    "settled_amount": "1000000000000000000",
    "fee": "50000000000000000"
  },
  "signature": "hmac_sha256_signature_here"
}

Event Types

Event TypeDescriptionWhen
verification.completedVerification succeededPayment verified
verification.failedVerification failedInvalid payment
settlement.completedSettlement succeededFunds transferred
settlement.rejectedSettlement rejectedSettlement failed

Security Checklist

1

Verify Signatures

ALWAYS verify the HMAC signature before processing
if (!verifySignature(payload, signature)) {
  return res.status(401).send();
}
2

Use HTTPS

Webhook URLs must use HTTPS in production ❌ http://your-app.com/webhookshttps://your-app.com/webhooks
3

Return 200 Quickly

Acknowledge receipt within 5 seconds
// Do this ✅
res.status(200).json({ received: true });
processWebhook(payload);  // Async

// Not this ❌
await longRunningProcess();  // Takes 30s
res.status(200).send();
4

Handle Idempotency

Store event IDs to prevent duplicate processing
const processedEvents = new Set();

if (processedEvents.has(payload.event_id)) {
  return res.status(200).json({ received: true });
}

processedEvents.add(payload.event_id);
processWebhook(payload);

Retry Logic

If your endpoint fails, 0xmeta.ai retries with exponential backoff:
AttemptDelay
1Immediate
21 minute
35 minutes
415 minutes
530 minutes
61 hour
After 6 attempts, webhook is marked as failed.

Production Deployment

1. Environment Variables

# .env
WEBHOOK_SECRET=your_webhook_secret_here
WEBHOOK_URL=https://your-app.com/webhooks/settlement

2. Load Balancer Configuration

If behind a load balancer, ensure:
  • Timeout is > 10 seconds
  • Keep-alive enabled
  • Proper header forwarding

3. Monitor Webhooks

// Log all webhook deliveries
app.post("/webhooks/settlement", (req, res) => {
  console.log("Webhook received:", {
    event_id: req.body.event_id,
    event_type: req.body.event_type,
    timestamp: new Date(),
  });

  // ... verify and process
});

// Alert on failures
if (failedWebhooks > 5) {
  await alertTeam("Multiple webhook failures detected");
}

Common Issues

Check:
  1. Is URL accessible? curl -X POST https://your-url/webhooks
  2. Is it HTTPS? (required in production)
  3. Is firewall blocking?
  4. Check webhook logs in dashboard (if available)
Fix: 1. Ensure you’re using correct secret 2. Serialize payload the same way (sorted keys, no spaces) 3. Use constant-time comparison 4. Check for encoding issues
Fix: 1. Return 200 immediately 2. Process async 3. Don’t do long operations in webhook handler
Fix:
// Store event IDs
const processed = await db.webhook_events.findOne({
  event_id: payload.event_id
});

if (processed) {
  return res.status(200).json({ received: true });
}

Testing Checklist

  • Webhook URL accessible via HTTPS
  • Signature verification working
  • Returns 200 within 5 seconds
  • Processes webhooks asynchronously
  • Handles duplicate event IDs
  • Logs all webhook deliveries
  • Alerts on failures
Never skip signature verification! Anyone can POST to your webhook URL.
Use webhooks instead of polling for 100x better performance and user experience.