API Reference
Webhooks
Delivery headers, event payloads, retries, and delivery logs.
Callback URL
Configure the team callback URL in the developer integration settings. MakePay checks the MakePay product settings first and then the shared developer callback settings, so existing MakeSwap webhook settings can receive MakePay payment events while the product-specific setting is being rolled out.
When you save a MakePay callback URL, MakeCrypto generates a webhook secret. Store it in your backend secret manager. You can regenerate it any time from the same settings screen; after regeneration, update your backend before enforcing signatures with the new secret.
Delivery behavior
MakePay sends payment and subscription webhooks as POST requests with a JSON body. Failed deliveries are retried up to ten times at five-minute intervals. Manual resend is available from MakeCrypto webhook request logs.
Headers
content-type: application/json
user-agent: MakePay-Webhooks/1.0
x-makepay-delivery-id: 9f1c6cf4-8514-4ee5-80fd-8e8fe2b5e313
x-makepay-delivery-group-id: 9f1c6cf4-8514-4ee5-80fd-8e8fe2b5e313
x-makepay-delivery-origin: event
x-makepay-event: status_changed
x-makepay-attempt: 1
x-makepay-signature: t=1776556800,v1=7d4b3f...
Verify signatures
MakePay signs the exact raw JSON request body with HMAC-SHA256. The signature payload is:
{timestamp}.{raw_request_body}
The x-makepay-signature header contains the Unix timestamp and versioned digest:
t=1776556800,v1=<hex_hmac_sha256>
Example verification in Node.js:
import crypto from "node:crypto";
const WEBHOOK_SECRET = process.env.MAKEPAY_WEBHOOK_SECRET!;
const TOLERANCE_SECONDS = 300;
export function verifyMakePayWebhook(input: {
rawBody: string;
signatureHeader: string | null;
}) {
if (!input.signatureHeader) {
return false;
}
const parts = Object.fromEntries(
input.signatureHeader.split(",").map((part) => {
const [key, value] = part.trim().split("=");
return [key, value];
}),
);
const timestamp = Number(parts.t);
const signature = parts.v1;
if (!Number.isFinite(timestamp) || !signature) {
return false;
}
const now = Math.floor(Date.now() / 1000);
if (Math.abs(now - timestamp) > TOLERANCE_SECONDS) {
return false;
}
const expected = crypto
.createHmac("sha256", WEBHOOK_SECRET)
.update(`${timestamp}.${input.rawBody}`, "utf8")
.digest("hex");
const actualBuffer = Buffer.from(signature, "hex");
const expectedBuffer = Buffer.from(expected, "hex");
return (
actualBuffer.length === expectedBuffer.length &&
crypto.timingSafeEqual(actualBuffer, expectedBuffer)
);
}
Read the raw body before parsing JSON. If your framework parses the body first, configure the webhook route to expose the raw request bytes and verify those bytes before trusting the payload.
Payment payload
{
"deliveryId": "9f1c6cf4-8514-4ee5-80fd-8e8fe2b5e313",
"type": "makepay.payment.status_changed",
"createdAt": "2026-04-19T00:00:00.000Z",
"event": {
"type": "status_changed",
"trigger": "payment_status_reconcile"
},
"paymentLink": {
"id": "8d15bb78-d0f8-45ef-88d7-2a1f1f79644b",
"uid": "01hzy4k6p4w9y2x7e2z7n8a2xm",
"status": "active",
"publicUrl": "https://makepay.io/payment/01hzy4k6p4w9y2x7e2z7n8a2xm",
"expiresAt": "2026-04-19T12:00:00.000Z",
"amount": "129.99",
"currency": "USDT",
"asset": "ETH.USDT-0xdac17f958d2ee523a2206206994597c13d831ec7",
"label": "Website order #1042",
"description": "Checkout for order #1042",
"merchantOrderId": "order_1042",
"clientEmail": "buyer@example.com",
"clientId": null
},
"session": {
"id": "5b55f0bb-0ac4-4f7c-a1d1-0d9af19c3bbd",
"status": "complete",
"previousStatus": "pending",
"invoiceAsset": "USDT",
"invoiceAmount": "129.99",
"selectedSellAsset": "ETH",
"requiredSellAmount": "0.04",
"expectedBuyAmount": "129.99",
"destinationAddress": "0xmerchant...",
"depositAddress": "0xdeposit...",
"channelId": "channel_123",
"compositeChannelId": "ETH:channel_123",
"sourceChain": "ETH",
"expiresAt": "2026-04-19T00:30:00.000Z",
"settlement": {},
"errorMessage": null
}
}
Event types
status_changedfor payment session state changes.settlement_updatedwhen settlement data changes after status reconciliation.payment_request_expired,quote_expired, andpayment_cancelled_by_payerfor terminal non-payment outcomes.channel_createdwhen the payer accepts a quote and MakePay creates the deposit channel.subscription_status_changedwhen a MakePay subscription moves betweenactive,paused,overdue, orcancelled.
quote_created and quote_refreshed are internal payment timeline events and are not sent to callback URLs.
Subscription status payload
MakePay also sends a callback when a subscription status changes. The scheduler
marks a subscription overdue when the oldest unpaid billing cycle is at least
24 hours past dueAt; once no unpaid cycle is more than 24 hours overdue, the
scheduler moves the subscription back to active. Merchant and customer-portal
status changes use the same callback.
{
"deliveryId": "78c35c42-61fb-4dd3-94b7-2a7df998bb6f",
"type": "makepay.subscription.status_changed",
"createdAt": "2026-04-20T00:00:00.000Z",
"event": {
"type": "subscription_status_changed",
"trigger": "subscription_scheduler"
},
"subscription": {
"id": "f6b76460-a437-4a81-a59f-8fcbb18c0f0f",
"uid": "sub_premium_001",
"status": "overdue",
"previousStatus": "active",
"customerEmail": "buyer@example.com",
"label": "Premium plan",
"description": "Monthly subscription",
"amountUsd": "49.99",
"settlementAsset": "ETH.USDT-0xdac17f958d2ee523a2206206994597c13d831ec7",
"cadence": "monthly",
"billingIntervalUnit": "month",
"billingIntervalCount": 1,
"startAt": "2026-04-18T00:00:00.000Z",
"timezone": "Asia/Dubai",
"metadata": {
"clientId": "client_1042"
},
"createdAt": "2026-04-18T00:00:00.000Z",
"updatedAt": "2026-04-20T00:00:00.000Z"
},
"cycle": {
"id": "f303b3b3-26d8-42bc-8c10-91fa1445f507",
"subscriptionId": "f6b76460-a437-4a81-a59f-8fcbb18c0f0f",
"sequence": 0,
"dueAt": "2026-04-18T00:00:00.000Z",
"amountUsd": "49.99",
"paymentLinkId": "8d15bb78-d0f8-45ef-88d7-2a1f1f79644b",
"paymentLinkUid": "01hzy4k6p4w9y2x7e2z7n8a2xm",
"paymentUrl": "https://makepay.io/payment/01hzy4k6p4w9y2x7e2z7n8a2xm",
"status": "overdue"
},
"data": {
"previousStatus": "active",
"nextStatus": "overdue",
"reason": "cycle_one_day_overdue"
}
}
Delivery logs API
Use the webhook request route to inspect deliveries and retries.
GET /api/partner/v1/makepay/webhook-requests?limit=100
Optional filters include paymentLinkUid, deliveryStatus, and search.