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_changed for payment session state changes.
  • settlement_updated when settlement data changes after status reconciliation.
  • payment_request_expired, quote_expired, and payment_cancelled_by_payer for terminal non-payment outcomes.
  • channel_created when the payer accepts a quote and MakePay creates the deposit channel.
  • subscription_status_changed when a MakePay subscription moves between active, paused, overdue, or cancelled.

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.

Need partner setup help?

Open the payment link details view in MakeCrypto to copy the generated snippets for a real payment UID, or return to the portal to manage merchant settings.

Open portal