Webhooks

Register HTTPS endpoints to receive real-time payment events. Always verify signatures before processing.

Register endpoint

POST/api/v1/projects/:projectId/webhooks

Body: { "url": "https://…", "events": ["order.paid", "order.failed"] }

Permission: webhooks:write
Example
curl -s -X POST "https://pay.andgroupco.com/api/v1/projects/PROJECT_ID/webhooks" \
  -H "Authorization: Bearer tp_test_xxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://your-server.com/webhooks/towanpay",
    "events": ["order.paid", "order.failed"]
  }'

Events

EventWhen to use
order.paid
Order successfully paid — extend access, fulfill order
order.failed
Payment failed or expired — notify customer, retry billing
order.payment_pending_verify
Buyer reported payment or uploaded proof — awaiting merchant/admin verification
transaction.completed
Ledger transaction recorded

Subscription integrations

Subscribe to order.paid and order.failed. Order metadata (subscription_id, billing_period) is included in every order event payload.

Signature verification

Verify X-TowanPay-Signature using HMAC-SHA256 of the raw request body with your webhook secret. Use constant-time comparison to prevent timing attacks.

Node.js
import crypto from "crypto";

function verifyTowanPayWebhook(rawBody, signatureHeader, secret) {
  const expected = crypto
    .createHmac("sha256", secret)
    .update(rawBody, "utf8")
    .digest("hex");
  if (!signatureHeader || expected.length !== signatureHeader.length) return false;
  return crypto.timingSafeEqual(
    Buffer.from(expected, "hex"),
    Buffer.from(signatureHeader, "hex"),
  );
}

Example payloads

order.paid includes the order resource and project id. The gateway used is available on payment.session.created, order.payment_pending_verify (payment_session.gateway), and transaction.completed (transaction.gateway).

order.paid
{
  "id": "evt_xxxxxxxx",
  "event": "order.paid",
  "created_at": "2026-03-15T10:00:00.000Z",
  "livemode": false,
  "data": {
    "order": {
      "id": "ord_xxxxxxxx",
      "reference": "TP-202603-001",
      "status": "paid",
      "amount_minor": 9900,
      "currency": "CNY",
      "metadata": {
        "subscription_id": "sub_abc123",
        "plan": "pro",
        "billing_period": "2026-03"
      },
      "paid_at": "2026-03-15T10:00:00.000Z"
    },
    "project_id": "clxxxxxxxxxxxxxxxx"
  }
}
payment.session.created (includes gateway)
{
  "event": "payment.session.created",
  "livemode": false,
  "data": {
    "order": { "id": "ord_xxxxxxxx", "status": "unpaid" },
    "payment_session": {
      "id": "ps_xxxxxxxx",
      "gateway": "wechatpay",
      "status": "pending",
      "amount_minor": 9900,
      "currency": "CNY"
    },
    "project_id": "clxxxxxxxxxxxxxxxx"
  }
}

Management

GET/api/v1/projects/:projectId/webhooks
Permission: webhooks:read
PATCH/api/v1/projects/:projectId/webhooks/:id
Permission: webhooks:write
POST/api/v1/projects/:projectId/webhooks/:id/test

Send a test event to your endpoint.

Permission: webhooks:write
GET/api/v1/projects/:projectId/webhooks/:id/attempts

Delivery attempt history.

Permission: webhooks:read