Webhooks

MutoPay sends an HMAC-SHA256 signed webhook when a payment completes, fails, expires, or needs attention. This page documents every event, the payload shape, and the delivery/retry guarantees.

MutoPay sends a webhook to your configured URL whenever a payment changes to a terminal state or requires your attention. Payloads are signed with HMAC-SHA256 — always verify the signature before trusting the data. See Verify a webhook signature.

Configure

Set the webhook URL and retrieve the webhook secret per-channel at Settings → Channels. Each channel has its own URL and secret.

Events

EventWhen it fires
payment.completedPayment succeeded, funds settled in your wallet
payment.failedPayment attempt failed (on-chain revert, provider rejection, etc.)
payment.expiredPayment was not completed before expires_at
payment.underpaidCustomer sent less than the required amount
payment.kyc_requiredThe swap provider requested customer KYC — payment is paused, not terminal
payment.needs_manual_checkUnknown provider status — MutoPay support will investigate

kyc_required and needs_manual_check are non-terminal. When they resolve (e.g. KYC cleared → processingcompleted), no additional webhook fires for the recovery — the eventual payment.completed or payment.failed is your signal.

Payload

{
  "event": "payment.completed",
  "payment_id": "pay_abc123",
  "status": "completed",
  "amount_usd": 54.23,
  "amount_original": 50,
  "fx_rate": 1.0846,
  "currency": "EUR",
  "src_token": "USDC",
  "src_chain_id": "56",
  "src_amount": null,
  "dest_token": "USDC",
  "dest_chain_id": "137",
  "dest_amount": "54230000",
  "dest_decimals": 6,
  "external_id": "order_1042",
  "tx_hash": "0xabc...",
  "failure_reason": null,
  "completed_at": "2026-04-21T12:00:00Z",
  "timestamp": "2026-04-21T12:00:01Z"
}

Field notes

  • dest_amount is raw token base units. Divide by 10^dest_decimals for human-readable: 54230000 / 10^6 = 54.23 USDC.
  • external_id is the value you sent when creating the payment — use it for reconciliation.
  • tx_hash is the on-chain destination transaction (where the stablecoin arrived in your wallet).
  • failure_reason is populated for payment.failed and sometimes payment.expired.
  • timestamp is when MutoPay fired the webhook. completed_at is when settlement actually happened.

Delivery guarantees

  • Delivery is at-least-once. You may receive the same webhook more than once — idempotency is your responsibility (use payment_id + event as the dedupe key).

  • Your endpoint must respond with a 2xx status within 30 seconds to be considered delivered.

  • Non-2xx responses, timeouts, and network errors are retried up to 5 times with exponential backoff:

    AttemptDelay after previous failure
    21 minute
    35 minutes
    430 minutes
    52 hours
    (final)12 hours

    After the final attempt fails, delivery is abandoned. Use the merchant dashboard or GET /api/merchant/payments/:id to reconcile.

Request headers

POST /your/webhook HTTP/1.1
Content-Type: application/json
X-MutoPay-Signature: sha256=<hex-digest>
User-Agent: MutoPay-Webhook/1.0

Security

  • Always verify the signature before processing the payload. See Verify a webhook signature.
  • Never log the webhook secret. Rotate it via the channel settings if you suspect compromise.
  • Your endpoint should be HTTPS.

Test your endpoint

From Settings → Channels click Send test webhook. MutoPay will POST a signed sample payload to your configured URL — same shape, same headers, same signing algorithm as production.

See also