Webhooks

Overview

Webhooks allow you to receive real-time notifications when events occur in your Alternative Payments account. Instead of continuously polling the API to check for updates, webhooks push event data directly to your server as they happen.

Available Webhook Topics

Subscribe to the following webhook topics to receive notifications:

Topic
Description

payout_paid

Confirms that a payout was successfully settled.

payout_failed

Indicates a failed payout attempt.

customer_created

Triggered when a new customer is onboarded.

payment_succeeded

Confirms that funds were successfully captured.

payment_chargeback

Notifies when a dispute occurs. Impacts reconciliation and may require intervention.

payment_failed

Indicates a failed payment attempt.

invoice_paid

Confirms the invoice has been fully paid. Closes the accounts receivable lifecycle and links the payment to the customer obligation.

payment_method_added

Indicates a payment method was added. Useful for enabling Autopay.

payment_method_deleted

Indicates a payment method was removed. Also relevant for managing Autopay.

Managing Webhook Subscriptions

Subscribe to a Webhook

Create a new webhook subscription to start receiving event notifications.

Endpoint: POST /webhooks

Request Body:

{
  "endpoint_url": "https://your-server.com/webhooks",
  "secret_key": "your_secret_key",
  "topic": "invoice_paid"
}

Parameters:

  • endpoint_url (required) - Your HTTPS endpoint that will receive webhook events

  • secret_key (optional) - A secret key for authentication. If provided, it will be sent as a Bearer token in the Authorization header

  • topic (required) - The event type you want to subscribe to (see available topics above)

Authentication: If you provide a secret_key, all webhook requests to your endpoint will include it in the Authorization header as a Bearer token. Use this to verify the authenticity of webhook requests.

List Webhook Subscriptions

View all your active and inactive webhook subscriptions.

Endpoint: GET /webhooks

Query Parameters:

  • limit (optional, default: 100) - Number of items to return

  • after (optional) - Cursor for forward pagination

  • before (optional) - Cursor for backward pagination

  • topic (optional) - Filter by webhook topic

  • is_active (optional) - Filter by active status (true/false)

Unsubscribe from a Webhook

Remove a webhook subscription when you no longer want to receive notifications.

Endpoint: DELETE /webhooks/{subscription_id}

Parameters:

  • subscription_id (required) - The ID of the subscription to remove

Webhook Events

List Webhook Events

View the history of webhook events sent to your endpoints.

Endpoint: GET /webhooks/events

Query Parameters:

  • limit (optional, default: 100) - Number of items to return

  • after (optional) - Cursor for forward pagination

  • before (optional) - Cursor for backward pagination

  • topic (optional) - Filter by webhook topic

  • status (optional) - Filter by status: pending, sent, or failed

  • from_date (optional) - Filter events created after this date (ISO 8601)

  • to_date (optional) - Filter events created before this date (ISO 8601)

Webhook Payload Structure

All webhooks are sent as HTTP POST requests with a JSON payload containing the following fields:

{
  "entity_id": "5cbcf9c3-9378-4633-91f0-886fa172f360",
  "idempotency_key": "066a3fd0-b849-494f-87ba-d186a6e4b2cc",
  "timestamp": "2025-09-29T21:01:36Z",
  "topic": "invoice_paid",
  "data": {
    "customer_id": "170d05e3-b498-4547-af7c-985f1e85d9f7",
    "invoice_id": "5cbcf9c3-9378-4633-91f0-886fa172f360",
    "status": "paid"
  }
}

Payload Fields

  • entity_id - The ID of the primary resource related to this event

    • For invoice_paid → Invoice ID

    • For payment_method_added → Payment Method ID

    • For customer_created → Customer ID

    • For payout_paid → Payout ID

  • idempotency_key - A unique identifier for this webhook event. Use this to prevent processing duplicate webhooks

  • timestamp - ISO 8601 timestamp of when the event occurred

  • topic - The event type (see Available Webhook Topics)

  • data - Additional context about the event. The structure varies by topic but typically includes:

    • Related entity IDs (customer_id, invoice_id, etc.)

    • Current status of the resource

    • Other relevant information specific to the event type

Webhook Ordering

Webhooks are delivered in order. This means:

  • All webhooks for your account are sent sequentially based on when they were created

  • A webhook will not be sent until all previous webhooks have been successfully delivered

  • This ensures you receive events in the correct chronological order

Retry Logic

If your endpoint is temporarily unavailable or returns an error, Alternative Payments will automatically retry webhook delivery.

Retry Behavior

  • Maximum attempts: 5 retries

  • Backoff strategy: Exponential backoff with a 30-second base delay

  • Retry intervals: 1:30m, 4:30m, 13:30m, 40:30m, 2:02hrs (attempts 1-5)

The retry delay is calculated using the formula:

delay = 30 seconds × (3 ^ attempt_number)

What Happens After Max Retries

After reaching the maximum number of retry attempts:

  1. The webhook delivery is permanently paused for your account

  2. All pending and failed webhooks remain in queue but won't be automatically retried

  3. You must manually trigger a retry to resume webhook delivery

Manually Retrying Failed Webhooks

When webhooks reach max retry attempts, use this endpoint to resume delivery:

Endpoint: POST /webhooks/retry

Request: No body required

Response:

{
  "message": "success"
}

This endpoint will:

  • Reset the retry counter

  • Resume processing all pending and failed webhooks in order

  • Attempt to deliver webhooks again with the full retry logic

Processing Limits

To protect your endpoint from being overwhelmed:

  • The retry process sends a maximum of 10 webhooks per run

  • If more than 10 webhooks are pending, they will be processed in subsequent retry cycles

  • All webhooks are still delivered in order

Best Practices

Endpoint Requirements

  • Use HTTPS - Webhook endpoints must use HTTPS (not HTTP)

  • Return 2xx status codes - Your endpoint should return a 200-299 status code to indicate successful receipt

  • Respond quickly - Process webhooks asynchronously if possible. Acknowledge receipt immediately and process the event in the background

  • Handle timeouts - Implement proper timeout handling on your server

Security

  1. Verify webhook authenticity:

    • Set a secret_key when subscribing

    • Validate the Authorization header on incoming webhook requests

    • Check that the Bearer token matches your secret

  2. Use idempotency keys:

    • Store the idempotency_key from each webhook

    • Check for duplicate keys before processing to prevent double-processing

  3. Validate payload structure:

    • Always validate the JSON structure before processing

    • Check that required fields are present

    • Verify the topic matches your expected event type

Error Handling

  • Return proper status codes:

    • 200-299: Successfully received and will process

    • 300-599: Error (we will retry)

  • Monitor webhook events:

    • Use GET /webhooks/events to monitor delivery status

    • Set up alerts for failed webhooks

    • Check your webhook endpoint logs regularly

Testing

Before going live:

  1. Subscribe to a webhook topic in your staging/test environment

  2. Trigger test events (create a customer, pay an invoice, etc.)

  3. Verify your endpoint receives and processes the webhooks correctly

  4. Test your retry logic by returning error status codes temporarily

  5. Confirm your secret key validation works correctly


Example Implementation

Here's a basic example of a webhook endpoint handler:

import express, { Request, Response } from "express";
import crypto from "crypto";

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

const WEBHOOK_SECRET = "your_secret_key";
const processedWebhooks = new Set<string>();

interface WebhookPayload {
  idempotency_key: string;
  topic: "invoice_paid" | "payment_failed" | string;
  entity_id: string;
  data?: Record<string, any>;
}

app.post("/webhooks", (req: Request, res: Response) => {
  // Verify authentication
  const authHeader = req.headers.authorization ?? "";
  if (!authHeader.startsWith("Bearer ")) {
    return res.status(401).json({ error: "Unauthorized" });
  }

  const token = authHeader.replace("Bearer ", "");
  if (token !== WEBHOOK_SECRET) {
    return res.status(401).json({ error: "Invalid token" });
  }

  // Parse webhook payload
  const payload = req.body as WebhookPayload;
  const { idempotency_key, topic, entity_id, data = {} } = payload;

  if (!idempotency_key) {
    return res.status(400).json({ error: "Missing idempotency key" });
  }

  // Check for duplicates
  if (processedWebhooks.has(idempotency_key)) {
    return res.json({ message: "Already processed" });
  }

  // Handle topics
  switch (topic) {
    case "invoice_paid": {
      const invoiceId = entity_id;
      const customerId = data.customer_id;
      console.log(`Invoice ${invoiceId} paid by customer ${customerId}`);
      break;
    }
    case "payment_failed": {
      const paymentId = entity_id;
      console.log(`Payment ${paymentId} failed`);
      break;
    }
    default:
      console.log(`Unhandled topic: ${topic}`);
  }

  // Mark as processed
  processedWebhooks.add(idempotency_key);

  return res.json({ message: "Webhook received" });
});

const PORT = 5000;
app.listen(PORT, () => {
  console.log(`Server running on port ${PORT}`);
});

Troubleshooting

My webhooks are not being delivered

Check the following:

  1. Verify your endpoint is publicly accessible via HTTPS

  2. Confirm your endpoint returns a 2xx status code

  3. Check GET /webhooks/events to see webhook status

  4. Ensure your subscription is active (is_active: true)

  5. Verify your server isn't blocking incoming requests

Webhooks stopped after reaching max retries
  1. Fix the issue preventing webhook delivery (check your server logs)

  2. Call POST /webhooks/retry to resume delivery

  3. Monitor the webhook events to confirm successful delivery

Receiving duplicate webhooks

This can happen during retries. Always:

  • Check the idempotency_key before processing

  • Store processed keys in your database

  • Skip processing if the key was already handled

How do I test webhooks locally?

Use a tool like ngrok to expose your local server:

  1. Run ngrok http 5000 (or your local port)

  2. Use the HTTPS URL provided by ngrok as your endpoint_url

  3. Subscribe to a webhook with the ngrok URL

  4. Trigger events in your test environment


For additional help, refer to the FAQ or contact our support team.

Last updated

Was this helpful?