Skip to main content

Overview

Webhooks let you react to events in real time instead of polling the API. This guide covers setting up a subscription, verifying signatures, building a receiver endpoint, and handling events reliably.

Available events

EventDescription
payout.createdA new payout has been created
payout.screeningCompliance screening has started
payout.processingPayout approved and submitted to rail
payout.sentRail confirmed dispatch
payout.completedFunds delivered to payee
payout.failedPayout could not be completed
payout.returnedFunds returned after delivery attempt
payout.cancelledPayout was cancelled
payee.createdA new payee was created
payee.updatedPayee details were modified
payee.deletedA payee was deleted
batch.completedAll payouts in a batch have resolved
batch.failedBatch processing encountered errors

1. Create a webhook subscription

Register your endpoint URL and choose which events to listen for:
curl https://api.antonpayments.dev/v1/webhooks \
  -X POST \
  -H "Authorization: Bearer ak_test_..." \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://your-server.com/webhooks/anton",
    "events": [
      "payout.completed",
      "payout.failed",
      "payout.returned",
      "payee.created"
    ]
  }'
Response
{
  "data": {
    "id": "wbh_cng3q8s6ek9kc5qg1h4g",
    "url": "https://your-server.com/webhooks/anton",
    "events": ["payout.completed", "payout.failed", "payout.returned", "payee.created"],
    "signing_secret": "whsec_k8x2mN9pQr7...",
    "status": "active",
    "created_at": "2026-02-14T10:00:00Z"
  }
}
Store the signing_secret securely. You will need it to verify webhook signatures. It is only returned once at creation time.

2. Verify webhook signatures

Every webhook delivery includes an HMAC-SHA256 signature in the Anton-Signature header. Always verify this signature before processing the event to confirm it originated from Anton. The signature header contains a timestamp and signature:
Anton-Signature: t=1708000000,v1=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8bd

Verification logic

  1. Extract the timestamp (t) and signature (v1) from the header
  2. Construct the signed payload: {timestamp}.{raw_request_body}
  3. Compute HMAC-SHA256 using your signing secret
  4. Compare the computed signature with the received signature
  5. Reject if the timestamp is more than 5 minutes old (replay protection)

3. Build your endpoint

Your webhook endpoint must:
  • Accept POST requests with a Content-Type: application/json body
  • Verify the Anton-Signature header on every request
  • Return a 2xx status code within 30 seconds
  • Be publicly accessible over HTTPS
const express = require("express");
const crypto = require("crypto");

const app = express();
const SIGNING_SECRET = process.env.ANTON_WEBHOOK_SECRET;

// Use raw body for signature verification
app.use("/webhooks/anton", express.raw({ type: "application/json" }));

function verifySignature(payload, header, secret) {
  const parts = Object.fromEntries(
    header.split(",").map((p) => p.split("="))
  );
  const timestamp = parts["t"];
  const signature = parts["v1"];

  // Reject if timestamp is older than 5 minutes
  const age = Math.floor(Date.now() / 1000) - parseInt(timestamp);
  if (age > 300) {
    throw new Error("Webhook timestamp too old");
  }

  const signedPayload = `${timestamp}.${payload}`;
  const expected = crypto
    .createHmac("sha256", secret)
    .update(signedPayload)
    .digest("hex");

  if (!crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected))) {
    throw new Error("Invalid webhook signature");
  }
}

app.post("/webhooks/anton", (req, res) => {
  try {
    verifySignature(
      req.body.toString(),
      req.headers["anton-signature"],
      SIGNING_SECRET
    );
  } catch (err) {
    return res.status(401).json({ error: err.message });
  }

  const event = JSON.parse(req.body);

  switch (event.type) {
    case "payout.completed":
      // Mark the invoice as paid in your system
      markInvoicePaid(event.data.reference);
      break;

    case "payout.failed":
      // Alert your team and flag for retry
      notifyPayoutFailed(event.data);
      break;

    case "payout.returned":
      // Credit the funds back in your system
      handlePayoutReturn(event.data);
      break;

    case "payee.created":
      // Sync payee to your system
      syncPayee(event.data);
      break;
  }

  // Always return 200 quickly -- do heavy processing async
  res.status(200).json({ received: true });
});

app.listen(3000);

4. Handle failures gracefully

1

Return 200 immediately

Acknowledge receipt quickly. If processing takes time, queue it for async handling. Anton times out after 30 seconds and will retry.
2

Make it idempotent

You may receive the same event more than once (retries). Use the event.id to deduplicate — store processed event IDs and skip duplicates.
3

Handle out-of-order delivery

Events might arrive out of order. A payout.completed could arrive before payout.processing. Use the created_at timestamp or the payout’s actual status to determine the true state.

Retry and delivery behavior

Anton retries failed webhook deliveries with exponential backoff:
AttemptDelay
1Immediate
21 minute
35 minutes
430 minutes
52 hours
68 hours
After 6 failed attempts, the event is marked as failed. You can view failed deliveries and manually retry them via the API or merchant portal. A delivery is considered failed if:
  • Your endpoint returns a non-2xx status code
  • The request times out after 30 seconds
  • A connection cannot be established

5. Monitor deliveries

Check the delivery history for a webhook event:
curl https://api.antonpayments.dev/v1/webhooks/events/evt_abc123/deliveries \
  -H "Authorization: Bearer ak_test_..."
This returns each delivery attempt with status codes, response times, and error details.

Testing locally

For local development, use a tunnel service to expose your local endpoint:
# Using ngrok
ngrok http 3000

# Your webhook URL will be something like:
# https://abc123.ngrok.io/webhooks/anton
Register this temporary URL as your webhook endpoint in sandbox.

Best practices

Respond fast

Return 200 within 5 seconds. Queue heavy processing for later.

Verify signatures

Always validate the Anton-Signature header. Reject requests with invalid or missing signatures.

Deduplicate

Track event IDs to prevent processing the same event twice.

Log everything

Log the full event payload for debugging and audit.

Set up alerts

Monitor for delivery failures — if your endpoint goes down, events queue up.

Use HTTPS

Your webhook endpoint must use HTTPS with a valid TLS certificate.