Xtopay Docs
Webhooks

Webhooks

Get notified in real time when payments, subscriptions, and billing events occur.

Overview

Xtopay sends HTTP POST requests to your configured endpoint whenever an event occurs — a payment succeeds, a subscription renews, a refund is processed. Webhooks are the authoritative source of truth for your integration: don't poll the API, listen for events.

Registering an endpoint

Go to Dashboard → Settings → Webhooks → Add endpoint. Enter:

  • URL — your public HTTPS endpoint (e.g. https://yourapp.com/webhooks/xtopay)
  • Events — choose which event types to receive, or subscribe to all

Xtopay delivers a test event immediately to verify your endpoint is reachable.

Webhook payload

{
  "id": "evt_a1b2c3d4",
  "type": "payment.succeeded",
  "created_at": "2026-05-24T10:02:14Z",
  "data": {
    "id": "pay_xyz789",
    "amount": 5000,
    "currency": "GHS",
    "status": "succeeded",
    "customer": { "id": "cus_abc123", "email": "ama@example.com" },
    "metadata": { "order_id": "ord_001" }
  }
}

Handling webhooks

import { headers } from "next/headers";
import Xtopay from "@xtopay/node";

const xtopay = new Xtopay({
  clientId: process.env.XTOPAY_CLIENT_ID!,
  clientSecret: process.env.XTOPAY_CLIENT_SECRET!,
});

export async function POST(req: Request) {
  const body = await req.text();
  const signature = (await headers()).get("x-xtopay-signature")!;

  let event;
  try {
    event = xtopay.webhooks.constructEvent(body, signature);
  } catch {
    return new Response("Invalid signature", { status: 400 });
  }

  // Always respond 200 quickly, do work async
  switch (event.type) {
    case "payment.succeeded":
      await fulfillOrder(event.data.metadata.order_id);
      break;
    case "subscription.renewed":
      await extendAccess(event.data.customer.id, event.data.current_period_end);
      break;
    case "subscription.cancelled":
      await revokeAccess(event.data.customer.id);
      break;
  }

  return new Response("ok");
}

Always verify the webhook signature before trusting the payload. An unverified webhook could be forged.

Delivery and retries

Xtopay considers a webhook delivered if your endpoint responds with any 2xx status within 10 seconds. If it doesn't, Xtopay retries with exponential backoff:

AttemptDelay after previous
1st retry5 minutes
2nd retry30 minutes
3rd retry2 hours
4th retry5 hours
5th retry10 hours
Give upAfter 72 hours

Failed deliveries are visible in Dashboard → Webhooks → Delivery log. You can manually replay any event from there.

Next steps

On this page