WhatsApp

Why was my WhatsApp template rejected?

"Rejected" gets used for at least three things that happen at different stages, carry different fields, and need different responses. Meta refusing your content is only one of them, and it is the one with the most written about it already. The other two are where people lose an afternoon.

The question worth asking first is not "why was it rejected" but "how far did it get".

What does the status actually say?

Each language of a template carries its own status, and four values matter here. The dashboard shows them per language on the templates page.

StatusHow far it got
rejectedMeta reviewed the content and refused it
submit_failedThe submission did not complete, so no review happened
outcome_unknownThe submission reached WhatsApp and no answer came back
pendingUnder review, nothing has gone wrong

rejected is the only one of these that means a human or a classifier at Meta looked at your words. The knowledge base covers what they look for, and WhatsApp template guidelines is the page to read when your status genuinely is rejected: it has the content buckets rejections fall into and what to do about each.

The rest of this page is about the other two, because nothing else documents them and both are easy to misread as a content problem.

Why did my template fail without ever being reviewed?

Because submit_failed covers two different stories, and neither is about your wording.

Bird refused to submit it. Some problems are visible before anything leaves for WhatsApp, and submitting anyway would just spend a review on a request that cannot succeed. The reason is written into the language in plain language: a derived WhatsApp template name already taken by a different template on that WhatsApp Business account, no usable credential for the account, a media header with no uploaded asset to submit, or stored content that could not be prepared for WhatsApp. Media has its own set: a URL that is not HTTPS, one that redirects further than Bird will follow, one that redirects to a non-HTTPS address, a file larger than WhatsApp accepts for that header format, a host Bird will not fetch from, a URL that could not be fetched, or a file whose type does not match the header format it was given.

Bird could not get it there. The retry ladder ran out. The reason then says the submission could not be delivered to WhatsApp within the retry window and was not accepted, or, when the failure was upstream of the send, that the media could not be prepared within the retry window so the submission was never sent.

The difference matters because the first is something you fix and resubmit, and the second is worth resubmitting as it stands. That is safe rather than a duplicate risk: before writing an exhaustion reason, Bird asks WhatsApp whether it holds the template under the derived name, and records the failure only when it does not.

Read error.description on the language. That field is the one carrying which of these happened, and it is written to be read by a person rather than parsed.

How do I tell a WhatsApp refusal from Bird's own?

By whether a Meta error code was recorded on the failure.

When WhatsApp refuses the submission request itself, the failure carries meta_error_code, WhatsApp's most specific identifier for the refusal. That case is worth separating out because the content may be perfectly fine and the request was not. A media file WhatsApp refused to fetch or accept lands here too, even though it feels like a media problem of yours.

When no Meta code is recorded, it is usually one of Bird's own verdicts above. Usually rather than always, and the gap is worth knowing: Meta answers most specific refusals with a catch-all code and puts the real identity in a subcode, and when its response body carries neither, nothing is recorded. So an absent code is strong evidence of a Bird-side verdict, not proof of one. Read the description before concluding.

One exclusion before you apply that rule anywhere. outcome_unknown also carries an error object, also with no Meta code, and it is not a failure at all. Check the status first and handle that one separately, or the rule will file it as a Bird-side refusal.

Why is there no rejection reason on my rejected template?

Because the rejection object is returned only when Meta actually sent a reason, not whenever the status is rejected.

This is the trap most likely to cost you real time, because the obvious defensive check is exactly the wrong one. Branch on status, never on whether rejection is present. A language Meta refused without supplying a reason string is fully rejected and carries no rejection object at all, so code that treats a missing object as "not rejected" will happily go on trying to send a template that can never go out.

When the object is there it carries Meta's reason passed through unmodified, and sometimes a recommendation, which is Meta's own suggested fix. The recommendation arrives for some refusals and not others, so treat it as a bonus rather than as the explanation.

What should I do about outcome_unknown?

Nothing, which is unsatisfying and correct. It is neither an approval nor a refusal.

The status means the request went out to WhatsApp and no answer came back at all. A request that never got a response may still have arrived and been accepted, so the copy may be at WhatsApp under review, or it may not have landed. Bird records that it does not know rather than guessing, and says so in the language: the submission was sent to WhatsApp but no response came back, and its outcome is being resolved against WhatsApp.

Two consequences follow, and between them they answer what to do.

You cannot change that language yet. It counts as in flight, so writing it, deleting it and reverting it are all refused with E15020, This language is under review at WhatsApp and cannot be changed until the review finishes. The error says review, which reads oddly against a status that says unknown, but the effect is the one that matters: the language is not yours to touch while this is outstanding.

Do not resubmit on the strength of this value. If Meta did accept the original, resubmitting puts a second template against the first, and content already under review is refused.

Bird works to resolve it rather than handing it back to you, by asking WhatsApp what it holds. Three things can come back.

WhatsApp has a matching copy. If it holds a template under the derived name whose content matches what went out, that copy is adopted. The language is recorded as approved or rejected if WhatsApp has already decided, and pending otherwise. In practice pending is the common outcome, because the ordinary reason for an unknown is that the request did arrive and is now in review, so the next state you see is usually not a verdict but the normal wait for one.

WhatsApp has nothing under that name. The language is recorded as submit_failed with the exhaustion reason.

Neither is true. The language stays in flight rather than being stamped with a verdict that cannot be confirmed. A language that stays neither approved nor rejected is still in flight, and if it stays that way, contact support with the template id rather than resubmitting.

Read the language again rather than acting on outcome_unknown itself.

How do I fix it and try again?

Whichever of the four you are looking at, the mechanism is the same, and it surprises people who expect to edit in place: a submitted version is immutable. There is no correcting a rejected language where it stands.

Fixing anything means opening a fresh draft, writing that language again, and submitting it, which is a new review. That is true for a content rejection, for both shapes of submit_failed, and for anything else you want to change about a submitted version. Authoring WhatsApp templates walks the draft, check and submit loop, and what a WhatsApp message template is covers why a version behaves this way and how the three version pointers relate.

There is one thing to know about checking whether the resubmit worked, and it is the reason to read the version rather than the template. A resubmit that was refused may not appear on the template. The template's status and its languages both describe the version currently in service, so a template whose earlier version is approved goes on reading active, with the earlier version's languages listed, while the language you just resubmitted was refused on a version that never went into service. Reading the template after a resubmit can therefore show you a healthy template and tell you nothing about what you just submitted.

So read the version you submitted. On the command line, bird whatsapp templates versions languages get returns one language's state on one version, including its status and its reason, which is both the way to confirm a resubmit and the fastest way to see which of the four cases above you actually have.

Construa na mesma rede.

Uma chave de API de teste é sua imediatamente. A produção é desbloqueada quando adicionar um método de pagamento e verificar um remetente.

Comece com um canal.
Adicione os outros quando estiver pronto.

Uma chave API de teste é sua imediatamente. A produção é desbloqueada quando você adiciona um método de pagamento e verifica um remetente.

Usa Claude Code, Cursor ou Codex? Copie um prompt de configuração e o seu agente instala o Bird CLI e as skills por si. Escolha o seu:

Cursor