This page is the troubleshooting companion to the Runtime API and its endpoint reference. Those pages describe the contract; this one starts from the failure you are seeing and works back to the fix. The runtime health emails Dualhook sends link straight to the sections below.
How to Read a Runtime Failure
Every Runtime API failure falls into one of two families, and the first thing to establish is which one you have.
Dualhook refused the request before it reached Meta. The response is a small JSON envelope with a message and no numeric Meta code or fbtrace_id. Some refusals include a string code, such as connection_not_routable or asset_scope_conflict:
{"error":{"message":"Credential is not valid for this media object"}}
These are covered section by section on this page. Check the key, request, pace, or connection hold. A Dualhook 503 can instead indicate an internal or upstream service problem; contact support if it persists.
Meta rejected the request after Dualhook forwarded it. The response carries Meta's own error object with a numeric code, and often error_subcode, error_data.details, and fbtrace_id:
{"error":{"message":"(#132001) Template name does not exist in the translation","type":"OAuthException","code":132001,"fbtrace_id":"..."}}
For these, use the numeric code. The most common ones on the runtime path are listed under Meta Errors on the Runtime Path, and the full catalogue is the Meta API Error Reference.
Every response, success or failure, carries an X-Dualhook-Request-Id header. Log it next to the status; it is the fastest way for support to find the request.
Revoked Key
Symptom: HTTP 401 with {"error":{"message":"Invalid credential"}} on every request from one integration, while the connection shows as active in the dashboard.
Cause: the dh_live_ key was revoked and cannot be used again. Keys are revoked when a connection is removed, when Meta removes Dualhook as a partner and cleanup runs, when you rotate and the overlap for the old key ends, or when an administrator quarantines a credential. Running Embedded Signup again after a disconnect revives the connection but does not revive its key.
The most common shape: one server, scheduled job, or automation flow was never updated after a rotation, so most traffic succeeds and a steady trickle fails. The other shape is a fully dead integration: after a partner removal and re-onboarding, nothing succeeds until a new key is created.
Fix: open the connection in your dashboard and create a new key from the Overview tab. The plaintext is shown once. Replace it everywhere the old key is configured, including places that are easy to forget, such as a second environment, a cron job, or an n8n, Make, or Zapier credential. There is no way to reactivate a revoked key.
If you keep getting 401 with a key you just created, check for the usual transport mistakes: a truncated key, a trailing newline, a doubled Bearer, or a host that is still graph.facebook.com. See Diagnosing a rejected key.
Revoked and expired keys are cached for up to 24 hours and continue returning 401. Other authentication failures, such as an unknown or quarantined credential, trigger a five-minute 429 throttle after 20 rejections within a minute, with a Retry-After header. That is the same problem, not a rate-limit problem: stop retrying and fix the key.
Connection Not Routable
Symptom: HTTP 403 with "code":"connection_not_routable" and a reason such as billing_unpaid, meta_offboarded, or plan_limit_exceeded. The key is valid.
Cause: the connection is on a hold. Nothing is forwarded to Meta until the hold is released.
Fix: do not rotate the key. Open the connection in your dashboard to see the hold and what clears it. The full list of reasons and their meaning is in connection_not_routable. Billing holds clear automatically once the invoice is paid; Meta-side holds clear when Meta's condition is resolved and, for a removed partner, after Embedded Signup is run again.
Media Rejected
Symptom: HTTP 403 with Credential is not valid for this media object on GET /v25.0/{MEDIA_ID} or GET /v25.0/{MEDIA_ID}/content, or Meta error 100 with subcode 33 on the same routes. Retrying the same ID shortly afterwards returns HTTP 429 with This object was recently rejected by Meta as an invalid media ID.
Cause: Meta no longer serves that media ID. Meta documents a 7-day expiry for media IDs received in webhooks and a 30-day expiry for IDs returned by the upload API, and a media ID is only readable by the WhatsApp Business Account that received or uploaded it. Dualhook asks Meta once, and when Meta answers with a definitive denial, Dualhook remembers it briefly so a retry loop does not keep paying for the same rejected lookup.
Fix: download inbound media promptly after the webhook arrives, store what you need, and treat a 403 on a media ID as final. If you fetch media for several phone numbers, make sure each request uses the key of the connection that received that message; a media ID from one WABA cannot be read with another connection's key.
These are media ID lifetimes. A download URL returned by Meta has its own shorter validity; see Meta’s media documentation.
Wrong Connection
Symptom: HTTP 403 with Credential is not valid for this phone number, ... for this WhatsApp Business Account, ... for this template object, or asset_scope_conflict. Passing a phone number ID or WABA ID to a media-only route can instead return 400.
Cause: each dh_live_ key is bound to exactly one connection: one phone number ID, one WABA ID, one organization. The request named an asset that belongs to a different connection than the key that was used. asset_scope_conflict is the special case where the same phone number is assigned to another active connection, or the WABA is assigned to an active connection in another organization. Multiple numbers in one WABA in the same organization are allowed.
Fix: check which key each integration uses and match it to the phone number ID or WABA ID in the request path. Multi-number setups need one key per connection, selected per request. For asset_scope_conflict, ask support to resolve the conflicting ownership; do not delete a working connection without checking which number and organization it serves.
Unsupported Field
Symptom: HTTP 400 with Unsupported fields value, Unsupported query parameter: ..., or Unsupported credential or scope field: ....
Cause: the Runtime API projects responses through a field allowlist per endpoint, and only accepts documented query parameters. Fields that work on graph.facebook.com directly, such as owner_business_info or credential-shaped fields, are rejected rather than forwarded.
Fix: compare your fields value with the supported list for that endpoint in the endpoint reference and drop the rest. An unsupported query response names the parameter. Unsupported fields value can be generic, so compare every requested field with the endpoint allowlist. Note that ?fields=health_status drops the top-level id; request ?fields=id,health_status if you need both.
Invalid Request
Symptom: HTTP 400, 409, 413, or 415 with a message that names a field, a limit, or a content type, such as Unsupported messages payload: to: Required, Content-Type must be application/json, GET requests must not include a body, or Media file exceeds the limit for its MIME type.
Cause: the request failed Dualhook's validation before it was forwarded. The runtime enforces the Cloud API schema for each operation, exact content types, bounded JSON, MIME-specific media limits, and safe pagination cursors. 409 means the operation is not available for the connection mode, for example a business profile update on a Coexistence connection.
Fix: read the message. Schema failures name the path of the offending field. For media uploads, the Content-Type must be multipart/form-data with a boundary, the file part must be last, and the size limit depends on the MIME type; see Input and Size Limits. For 409, use the WhatsApp Business app for that setting instead.
Rate Limited
Symptom: HTTP 429 with Rate limit exceeded and a Retry-After header.
Cause: the connection exceeded one of the per-connection buckets: 80 messages per second, 120 media reads per minute, 20 media mutations per minute, 6 phone or WABA health reads per minute, or the weighted management budgets. The limits are per connection and per operation type.
Fix: honour Retry-After, and pace requests per connection rather than firing bursts. Management requests also share WABA budgets, and health reads have a separate WABA ceiling. Smoothing bursts over time helps stay within both scopes. The full table is in Rate Limits.
Failure Circuit
Symptom: HTTP 429 with Sending is temporarily paused after repeated failed Meta requests on POST /messages.
Cause: 30 or more sends from this connection failed at Meta within five minutes, so sending is paused for five minutes. A successful send in that window does not erase the failures already counted. This limits repeated failing traffic. It does not diagnose a quality-rating or spam problem.
Fix: fix the Meta error that caused the failures first; it is the one immediately below the 429s in your logs, and usually a template or parameter error covered in the next section. Then honour Retry-After. Requests refused during the pause do not extend it. More failed sends after it ends can open a new pause.
Meta Errors on the Runtime Path
These are the Meta codes that come back most often through the runtime. Each has its own page.
| Code | Meaning | Page |
|---|---|---|
100 | Invalid parameter | Error 100 |
131008 | Required parameter missing | Error 131008 |
131009 | Parameter value not valid | Error 131009 |
132001 | Template does not exist | Error 132001 |
132000 | Template variable count mismatch | Error 132000 |
132012 | Template parameter format mismatch | Error 132012 |
131056 | Pair rate limit | Error 131056 |
130429 | Throughput limit | Error 130429 |
131000 | Meta internal error | Transient; retry with backoff |
2 | Meta temporary error | Transient; retry with backoff |
Two of these are not yours to fix: 131000 and 2 need retry/backoff and an upstream-status check, rather than a customer configuration change. Persistent 131000 errors need support investigation. Dualhook does not email about them.
The Runtime Health Emails
When a connection keeps failing for one of the reasons above, Dualhook emails the organization's admins once per issue with the count, the affected connections, and a link to the matching section of this page. The rules:
- One email per issue type, at most once every 7 days, even if the failures continue.
- An issue is reported once it reaches 20 failed requests and 5% of the connection's traffic over the most recent 7 Europe/Brussels calendar days, including the current partial day. Thresholds are checked per connection before qualifying connections are combined into one email. Rate limiting has a higher bar of 100 requests and 10%.
- At most two reports per organization are sent in one daily run.
- Only actionable failures are reported. Meta’s transient errors, Dualhook internal errors, quarantined credentials, and administrative holds do not generate these reports.
- A revoked key on a connection with no successful requests is reported as an integration outage; a revoked key alongside successful traffic is reported as a partial rotation.
- If the organization's subscription is no longer active, only one email is ever sent, and only for revoked/expired keys or connection/billing holds, so a leftover integration can be switched off. A billing hold alone does not revoke the key.
The emails are opt-out under Runtime API health reports on the Compliance page of your dashboard, and every email carries an unsubscribe link for that category.
What to Send Support
- The
X-Dualhook-Request-Idfrom the failing response. - The timestamp, method, and path, with the phone number ID or WABA ID.
- The full response body. Never send the key itself; the prefix shown in the dashboard is enough to identify it.