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:
| Attempt | Delay after previous |
|---|---|
| 1st retry | 5 minutes |
| 2nd retry | 30 minutes |
| 3rd retry | 2 hours |
| 4th retry | 5 hours |
| 5th retry | 10 hours |
| Give up | After 72 hours |
Failed deliveries are visible in Dashboard → Webhooks → Delivery log. You can manually replay any event from there.