Core

Webhooks & signing

Subscribe to events with HMAC-SHA256 signed deliveries.

Subscribe to event types from your dashboard or via the API. Each delivery includes a signed header so you can verify the payload was issued by MoonPoay.

text
X-MoonPoay-Signature: t=1714672890,v1=2c8a8…b7
X-MoonPoay-Event: payment.completed
X-MoonPoay-Delivery: dlv_1zP9e…

Verifying a delivery

javascript
import crypto from "node:crypto";

export function verify(payload, header, secret, toleranceSec = 300) {
  const parts = Object.fromEntries(
    header.split(",").map((p) => p.split("=")),
  );
  const t = Number(parts.t);
  const expected = crypto
    .createHmac("sha256", secret)
    .update(`${t}.${payload}`)
    .digest("hex");
  if (!crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1))) {
    throw new Error("invalid_signature");
  }
  if (Math.abs(Date.now() / 1000 - t) > toleranceSec) {
    throw new Error("stale_timestamp");
  }
}
Always verify with a constant-time comparison. Reject any delivery whose timestamp is more than 5 minutes old to defend against replay.

Event types

  • payment.created
  • payment.processing
  • payment.completed
  • payment.failed
  • payment.expired
  • refund.created
  • refund.succeeded
  • refund.failed
  • checkout.session.completed
  • checkout.session.expired
  • settlement.completed
  • chargeback.created

Register an endpoint

bash
curl https://sandbox-api.key2pays.com/v1/webhooks \
  -H "Authorization: Bearer sk_test_51N8mP...exampleK3Y" \
  -H "Content-Type: application/json" \
  -d '{
        "url": "https://example.com/webhooks/moonpoay",
        "events": ["payment.completed", "payment.failed", "refund.succeeded"]
      }'