Message delivery errors
What a failed WhatsApp send looks like on the message record, the stable codes it maps to, and how to troubleshoot deliverability failures like Meta 131026.
Where failures surface
When a WhatsApp send fails, the message record carries three fields you can read at GET /v1/messages/{message_id}: error_code — a stable, registered Tyxter code that is safe to branch on; error_message — a human-readable reason that, for a mapped code, ends with the next step to take; and provider_error — selected Meta error fields (such as numeric code, error_subcode, detail text, and Meta's fbtrace_id), bounded for diagnosis. Failures reach you two ways: rejected inline when you create the message, or reported later by Meta and delivered to your webhook endpoint as a message.failed event. Both paths record the same fields on the message, and the message.failed webhook payload carries error_code, error_message and provider_error inline — a receiver never has to call back to learn why the send failed.
The bounded mapping
error_code is bounded — it is never a raw provider string. Known Meta failure codes map to specific Tyxter codes, and everything else maps to provider_error with the Meta detail preserved:
- Meta
190maps toprovider_connection_unauthorized: the stored WhatsApp connection credential is expired or revoked. Every failed message includes an organization-targeted/connect-whatsapp?organization_id=…link to re-run Embedded Signup. Retrying without reconnecting cannot succeed. - Meta
131047maps toservice_window_required: a free-form message was sent outside the 24-hour customer service window (opened by an inbound message from the recipient). Send an approved template instead — list them at GET /v1/templates. - Meta
131008(template messages), plus132000and132007, map totemplate_param_mismatch: this is the asynchronous fallback when Meta rejects an already accepted send because a required template parameter is missing, the template name, language, or parameters no longer match the approved version, or the provider behavior is not modeled. For other message types it remainsprovider_errorbecause Meta uses that code generically. Compare the send against the approved version before retrying. Modeled exact POSITIONAL/NAMED BODY keys, named text-HEADER and dynamic URLparameter_namevalues, media headers, and dynamic URL-button parameters instead fail synchronously with400 template_param_mismatchbefore a message exists, so they carry noprovider_error. - Meta
132001maps totemplate_not_approved: Meta could not resolve the exact template name and language as approved in the sending WhatsApp Business Account. Check that exact name and language, then confirm approval before sending again. This terminal receipt does not identify why Meta could not resolve the template; the raw Meta code remains inprovider_error.code. - Meta
131042maps tometa_business_eligibility_payment_issue: Meta blocked the send over the business's billing setup. Complete the business tax information and full address, then add or update the payment card in the billing section of Meta Business settings. Every send from the account fails until it clears, so this one is worth alerting on. - A WhatsApp
/messagessend rejected with HTTP403,OAuthException, and numeric Meta code200recordsmeta_messaging_permission_missing. Meta uses the same refusal for an account with no payment method or an inactive account, so Tyxter first checks the sending account's health: a confirmed payment or inactive-account block is reported with its own code instead. When health shows no such block or cannot be read, the permission code stands and the message is not retried. In Meta Business Settings, open Users > System users, select the Tyxter system user, and make sure it has full control of this WhatsApp account, including messaging. If it already does, reconnect WhatsApp with every requested permission checked, then contact Tyxter support if Meta still refuses. The failed message keeps Meta's bounded detail inprovider_error, and the connection stays active. This rule does not apply to media preparation, Instagram sends, or later status updates.
WABA send-capability blocks
- A fresh stored WABA block terminalizes a queued send before Meta is called, with a stable
error_codeandprovider_error: null. When a Meta send error causes reactive health confirmation to prove that the effective sender WABA is blocked, the same terminal receipt is not retried unchanged and retains the original bounded send failure inprovider_error, even when its provider code was generic. Meta health code141006recordsmeta_payment_method_required: add a payment method in WhatsApp Manager → Billing & payments.141008recordsmeta_waba_inactive: reactivate the WABA in WhatsApp Manager or ask Meta support.141011recordsmeta_messaging_permission_missing: reconnect and grant the requested WhatsApp messaging permissions, then ask support if it persists. If Meta reports more than one recognized diagnosis, remediation is deterministic: payment, inactive WABA, then messaging permission. This does not suspend the connection or phone number, and inbound messages remain available. After fixing the account, submit a new message once five minutes have elapsed since the last health observation or check attempt. A valid Meta healthAVAILABLEorLIMITEDresponse with no recognized WABA/APP block codes clears the stored block; a141010verification advisory alone does not retain it. Previously failed messages remain terminal. Read the connection's optionalwaba_send_capabilitiesentry matching the senderwaba_idfor the latest trusted diagnosis. The singularsend_capability,send_block_codes, andsend_capability_observed_atfields are the compatibility view for the connection's primary WABA. - Meta
131037maps tometa_display_name_not_approved: a WhatsApp-provided (+1 555) number needs a display name. In WhatsApp Manager, go to Phone numbers and set the display name, then wait for Meta to approve it before sending again. This terminal receipt does not pause the sending number. - Meta
131053maps tometa_media_download_failed: Meta could not fetch or could not use the media for the send — the host was unreachable or too slow, returned an error, or served a file whose real content did not match the type it claimed. When the send carried a link you control, that link is the thing to fix: make sure it is anonymously reachable and serves the correct type, because retrying the same link usually fails the same way. The most reliable fix is to stop sending links at all — upload the file at POST /v1/media/uploads and send it by asset_id. This code can also mean the bytes themselves are the problem, including for media Tyxter hosts: Tyxter validates themime_typeyou declare, not the bytes you send, so a HEIC photo saved as.jpgand declaredimage/jpegpasses upload and still fails here. Two cases can no longer reach this code. Media uploaded through POST /v1/media/uploads or sent inline on POST /v1/messages has its declaredmime_typechecked against WhatsApp's accepted set up front and is rejected withinvalid_media_mime_typeinstead of failing here. And a WhatsApp send carrying amedia.linkis no longer handed to Meta as a link at all — Tyxter retrieves the file itself and sends the uploaded copy, so a URL Tyxter cannot retrieve now reportsmedia_link_fetch_failedand a URL serving a type WhatsApp does not accept now reportsinvalid_media_mime_type, both before the send is submitted. See "Media links Tyxter retrieves" below. What still reaches131053is media Meta receives but cannot use, and the narrow link cases Tyxter does not retrieve. - Meta
131026maps tometa_message_undeliverable: Meta could not deliver to that recipient. Retry addressing them by phone number rather than a business-scoped user id, and remove links from the message if you are sending to one. See the section below. - Meta
131062maps tometa_business_scoped_id_mismatch: a business-scoped user id issued under one business portfolio was used to send from another. These ids belong to a single portfolio — send using the id issued by the portfolio that owns this phone number. - Meta
130472maps tometa_marketing_experiment_holdout: the recipient is in Meta's marketing-message holdout group, so Meta withholds marketing sends to them. This is an expected loss rather than a fault to fix; utility, service, and authentication messages are not affected. - Meta
131049maps tometa_marketing_frequency_cap: Meta withheld this marketing message because the recipient recently received too many marketing messages. Do not retry it today. This is scoped to the recipient and does not pause the sending number; utility, service, and authentication messages to the same recipient are not affected. - Anything else maps to
provider_error: Meta accepted the request shape but reported a failure Tyxter has no specific mapping for. Interpret the numeric code inside the message'sprovider_errorfield, which is always preserved.
Failures before Meta and unknown provider-media effects
Most codes below are stamped before any provider call, so those messages have no
provider_error: nothing was sent and nothing was billed. provider_send_result_missing
also covers an incomplete provider-media upload receipt. That case still has no
MessageAttempt, but its paid upload outcome is unknown and may have reached Meta
or incurred a charge, so it is terminal rather than replayed.
no_provider_connection: the environment has no WhatsApp connection at all, so there is nothing to send through. Connect WhatsApp from the dashboard, then resend. Keep it apart from the three codes that mean a connection does exist:provider_connection_unauthorized(the stored credential died — re-authenticate),provider_connection_suspended(Meta acted against the account — appeal), andmeta_registration_required(setup began and stopped before Meta finished registering the number — finish it from the dashboard link in the message text). Retrying without connecting cannot succeed.sender_phone_number_unavailable: an accepted message named a sending phone Tyxter cannot use. The receipt intentionally gives no more specific reason, does not fall back to the connection's primary number, and never reaches Meta. Choose an active WhatsApp phone linked to Meta, then resend. Older messages with no sender continue using their connection default for compatibility.invalid_template_payload: a template message reached the sender without bothtemplate.nameandtemplate.language, so there was no template to resolve. Resend with both fields set. It is not retryable unchanged.phone_number_rate_limit_exhausted: the send was rescheduled repeatedly because the sending phone number was over its throughput budget, and it was still over after the last reschedule. Nothing about the message is wrong — it lost a queue for capacity. Spread the traffic over a longer window, send from more numbers, or raise the phone number's throughput before resending.media_asset_resolution_failed: Tyxter could not resolve the media attached to the send and the failure carried no more specific cause. The expected media problems have their own codes and never land here —media_asset_not_found,media_asset_expired, andmedia_asset_not_readyfor the asset itself, andmedia_link_fetch_failed,invalid_media_mime_typeandmedia_too_largefor amedia.link. This is the residual bucket, so a repeat is worth reporting with the message'strace_idrather than retried indefinitely.media_voice_requires_ogg_opus: a requested WhatsApp voice note resolved to known audio whose normalized MIME is notaudio/ogg, so delivery stops before a provider call. Meta requires OGG/Opus mono audio, but Tyxter checks only the MIME it knows; it does not inspect codecs or channels, or convert media. Use OGG/Opus mono audio or omitmessage.media.voicefor ordinary audio.provider_send_result_missing: stale-send recovery found a message left insendingwith no provider message id and zero Meta attempts after its immutable creation age reached the effective queue-expiry bound, or a provider-media upload effect marker has no completion/cache receipt. Before that bound, the stale provider-safe shape is returned to durable queued work with a retry delayed by 60 seconds. An uncompleted upload effect is failed immediately because repeating unknown paid provider work could duplicate it. A Meta attempt that started but has no response or error is also never replayed automatically because Meta may already have accepted and billed the send. Report repeatedprovider_send_result_missingoccurrences or a message that remainssendingwith itstrace_id.
TTS render failures
TTS audio is rendered before WhatsApp is called. tts_provider_error means the configured speech provider rejected or could not complete the render after retries; the message's provider_error carries a bounded provider name and, when available, status_code. tts_render_failed means rendering failed outside a provider response, such as storage or unexpected processing failure. In both cases the message never reached WhatsApp. Use the message trace_id when reporting a repeated tts_render_failed.
Media links Tyxter retrieves
When a WhatsApp media message carries message.media.link, Tyxter now retrieves that file and uploads it to WhatsApp, then sends by the uploaded media id instead of passing your URL along. Nothing about the request changes: you still send media.link, GET /v1/messages/{message_id} still shows the message you created, and no new field appears. What changes is which failures you can see. Meta's own weblink fetch was the single largest source of 131053, and it gave you nothing to act on except the word "failed"; a retrieval Tyxter performs can tell you exactly what went wrong, and it lets the accepted-type and size rules that already apply to uploads apply to links too. One more benefit is quiet but real: a broadcast retrieves and uploads the file once per sending phone number rather than once per recipient, so your origin is not hit thousands of times.
media_link_fetch_failed is the stable code for that retrieval failing. It means Tyxter could not get the bytes — the host did not resolve, refused the connection, or was rejected as a private or internal address; the request timed out; the URL answered a non-2xx status; it redirected more than three times; or it returned an empty body. Some of those causes are transient (DNS blips, timeouts, connection resets, 429 and 5xx answers) and Tyxter retries them automatically, stamping the code only once the attempts are exhausted; the rest fail the message on the first attempt because retrying an unreachable or forbidden URL only repeats the same answer. To fix it, make the URL publicly reachable without authentication — https is strongly recommended and plain http is also accepted — serve the file bytes directly rather than an HTML preview or a login page, and answer within 30 seconds. The most reliable fix is to skip URLs entirely: upload the file once at POST /v1/media/uploads and send it by asset_id.
Two other codes can surface on the same retrieval, and both mean Tyxter got the bytes and refused them. invalid_media_mime_type means the URL answered without a Content-Type header at all, or with one WhatsApp does not accept for that media kind — the same accepted set POST /v1/media/uploads enforces, so a URL cannot deliver a type an upload of the same file would have been refused. media_too_large means the response exceeded the per-kind size limit; the download is cancelled mid-stream rather than read to the end.
Keep media_link_fetch_failed and meta_media_download_failed apart, because the remediation differs: the first means Tyxter could not retrieve your URL and the message never reached Meta, the second means Meta could not fetch or use media that was handed to it. For the same reason, media_link_fetch_failed never means a provider rejected your send — no provider was contacted.
Retrieval covers the media message type and the media header of an interactive reply-button message. A reply-button header carrying a link is retrieved and uploaded exactly like message.media.link — same accepted types, same size limits, and the same media_link_fetch_failed / invalid_media_mime_type / media_too_large codes when it fails; a header carrying id is a media id you already hold at Meta and is passed through as-is. Flow message headers — and the headers of interactive list messages — are text-only and never carry media at all. Template header media is always referenced by asset_id and is uploaded before the send, but a header image supplied as a raw link inside template.components bypasses this entirely and is passed through untouched. Still unretrieved: a link whose scheme is not http or https, channels whose provider has no media upload API (Instagram), and rare operational fallback modes. In sandbox, delivery is simulated and no URL is ever retrieved.
When Meta has acted against your WhatsApp account
provider_connection_suspended is the stable code for a send that never left Tyxter because Meta acted against the WhatsApp Business Account behind the connection — it was banned, rejected at account review, restricted, legacy-suspended with scheduled_for_disable, or the grant was lost because Tyxter's app was removed or the account was deleted. It is not a deliverability problem with the recipient and it is not a Meta-mapped code: the connection is suspended, so every send on it fails before any provider call and there is no provider_error to read. That is deliberately wider than Meta's own action — a restriction or a legacy scheduled-disable suspension still leaves some sending possible at Meta, and Tyxter stops anyway rather than pushing traffic through an account Meta has already flagged. A connected row with current SCHEDULE_FOR_DISABLE evidence is not this error: its nullable waba_ban_date is advisory, and sends continue until Meta actually disables the account.
Retrying cannot succeed until the account state changes, so treat it as an operator alert rather than a retryable failure. Read suspension_reason on the connection at GET /v1/provider-connections/{connection_id} for the machine-readable reason, or the whatsapp channel's reason on GET /v1/provider-connections/status, which reports policy_suspended for this case and token_suspended when the credential merely died. The remedy follows the reason: appeal the account at business.facebook.com/accountquality when it was banned, rejected at review, restricted, or legacy-suspended for disablement — replacing or re-authorizing the access token cannot lift any of those, because the credential is not what Meta acted on — and reconnect WhatsApp from the dashboard when the reason is app_uninstalled or account_deleted, where connecting again really is the fix.
Keep it apart from provider_connection_unauthorized, which means the stored credential expired or was revoked and re-authenticating restores sends. Its write-path twin is provider_connection_policy_suspended, the 409 a credential write (rotate, register, complete registration) gets for the same account state. Sends resume on their own once Meta clears the account and the connection returns to connected.
Meta 131026 — Message Undeliverable
The most common delivery failure on production sends is Meta code 131026 ("Message Undeliverable"), which maps to meta_message_undeliverable: Meta could not deliver to that specific recipient. It is recipient-specific and it is not transient — retrying the identical send will fail again. The strongest diagnostic is comparison: when the same template and sender deliver to other recipients, your template, sender, and Tyxter configuration are all working, and the cause is one of:
- The recipient cannot receive the message: the number is not an active WhatsApp account, runs a very old WhatsApp client, or the user has not accepted WhatsApp's current terms of service.
- Your WhatsApp Business Account has limited reach: an unverified business is capped to a small set of unique recipients contacted outside their customer-service windows. This is not a cap on the number of messages. Check the business verification status and messaging limits in Meta Business Manager / WhatsApp Manager.
- Meta declined this sender-to-recipient pair: the user blocked the sender, or Meta's per-user marketing-message limits throttled another marketing template to that user.
- You addressed a business-scoped user id and the message contains a link: the same
131026comes back. Retry addressing the recipient by phone number, or send the message without the link.
To self-diagnose from Tyxter: read the sender's health at GET /v1/phone-numbers/{phone_number_id} — the response carries the Meta quality rating and messaging-tier metadata — and read the failing message's provider_error detail at GET /v1/messages/{message_id}. A 131026 failure originates inside Meta's delivery network; the send left Tyxter correctly, so there is no Tyxter-side configuration to fix.
Sandbox
In sandbox, delivery is simulated and no real phone is ever contacted, the marketing_frequency_cap scenario reproduces Meta 131049 as meta_marketing_frequency_cap with provider_error.code: 131049, without pausing the sender. The ordinary sandbox failure scenarios stamp the registered codes sandbox_delivery_failure, sandbox_recipient_opted_out, and sandbox_template_rejected. These are deterministic terminal outcomes, carry no provider_error, and appear on the same message read and message.failed webhook surfaces as production delivery codes. See the Sandbox guide.
Meta 131050 is recorded as meta_marketing_opted_out: wait for the recipient to resume WhatsApp marketing for this business. Ordinary inbound and general API opt-in do not clear this preference. media_content_changed means uploaded bytes changed after completion; create and complete a fresh media upload.
Meta business account locked
Meta error 131031 is recorded as meta_business_account_locked on the failed message and its message.failed webhook. Older failures may still carry provider_error; inspect provider_error.code for 131031. This is an asynchronous delivery failure, not a new HTTP response from message creation.
Meta can lock one WhatsApp Business Account or the business portfolio that owns it without sending a ban webhook. Appeal at Meta Account Quality. Sends will fail until Meta reinstates the account. Meta's email may name a different WhatsApp account in the same portfolio, including a test account.
Tyxter requests an account-state check after a 131031 failure (at most once per connection per hour), or when a phone health report identifies WABA ban code 141014. A policy warning also schedules checks after five and thirty minutes. Warnings alone do not prove whether Meta permits sends. Only confirmed health evidence changes connection state; an unavailable check never becomes a ban. A confirmed ban emits provider_connection.suspended and subsequent sends fail with provider_connection_suspended. These timings describe queued checks, not a guaranteed detection deadline.
To reproduce the delivery receipt in sandbox, set metadata.sandbox_scenario to business_account_locked. It records meta_business_account_locked with provider_error.code: 131031 and appeal guidance, without contacting Meta or suspending a real connection.