Errors & Troubleshooting
MutoPay uses standard HTTP status codes with structured JSON error bodies. This page lists every error, what it means, and how to fix it.
MutoPay returns standard HTTP status codes. Every error body is JSON with at least an error field (machine-readable) and usually a message field (human-readable).
{
"error": "invalid_request",
"message": "amount_usd must be greater than 0"
}
HTTP status codes
| Status | Body error | Meaning & fix |
|---|---|---|
| 400 | invalid_request | Validation error. Check message — a field is missing, malformed, or out of range. |
| 401 | unauthorized | Missing or invalid API key. Check the header name (X-API-Key for channel, Authorization: Bearer for master) and that the key hasn’t been rotated. |
| 403 | merchant suspended | Your account is suspended. Contact support. |
| 403 | forbidden | You are trying to access a resource that doesn’t belong to you (e.g. GET /api/merchant/payments/pay_abc where pay_abc belongs to another merchant). |
| 404 | not_found | The payment/channel/resource doesn’t exist, or has been deleted. |
| 409 | conflict | State conflict — e.g. trying to create an admin when one already exists, or submitting an order for a payment that’s already completed. |
| 422 | unprocessable | Business-logic rejection — e.g. the selected token/chain combination is unsupported, or the amount is below the minimum for the chosen route. |
| 429 | rate_limited | Too many requests. Back off and retry with exponential delay. |
| 500 | internal_error | Our fault. Retry idempotent reads; for writes, check whether the operation succeeded before retrying. |
| 502/503/504 | — | Transient infrastructure error. Retry with backoff. |
Common gotchas
”invalid signature” on webhook verification
- Are you using the raw request body bytes, not a re-serialized JSON? Re-serializing changes whitespace and key order.
- Are you using the webhook secret for that channel, not the merchant’s master key or a different channel’s secret?
- Is your comparison constant-time? See Verify a webhook signature.
”payment expired” with a fresh payment
expires_atdefaults to 30 minutes from creation. Long-running checkout flows should pass an explicitexpires_atwhen creating the payment.- Set it in your call:
"expires_at": "2026-04-21T16:00:00Z". Max is 7 days.
”unauthorized” but the key is correct
- Channel keys go in
X-API-Key. Master keys go inAuthorization: Bearer. Swapping these returns 401. - Keys are shown once at creation. If you lost it, rotate and update your integration.
”merchant suspended” (403)
- Someone at MutoPay has paused your account. Email support with your merchant ID (the
idyou see in the dashboard URL or returned on any/api/merchant/*response).
Webhook never arrives
- Is the webhook URL configured on the channel that created the payment? Each channel has its own URL.
- Is your endpoint reachable from the public internet and served over HTTPS?
- Check Settings → Channels → test webhook. If the test fails, the issue is on your side.
- Returning a non-2xx? MutoPay retries 5 times over ~15 hours, then gives up. Check your server logs.
Getting help
- For integration issues: hello@mutopay.com with your merchant ID, the payment ID, and the full request/response including headers (redact keys).
- For account suspension or billing: reply to the email we sent you, or same address.
- Status page: status.mutopay.com (if active).
See also
- Authentication — which key to use where.
- Verify a webhook signature — fixes most webhook auth errors.