Skip to main content

Resend a Failed Message

Sends the stored request of a message that ended in FAILED or CANCELED again, without you having to keep the original payload. This creates a new message with its own id and correlationId. The original row is left exactly as it is — its status, its timestamps and its provider message id all stay readable, so the two attempts remain distinguishable afterwards.
The resend counts against your plan quota, the same as any send. It is a new message, not a retry of the old one.
Before building an automatic retry: when a message reaches FAILED, the platform has already retried it — up to 10 attempts 30s apart, plus a reconciler, plus a delivery check 30 and 60 minutes after the send. FAILED means we gave up.So a resend right away usually fails for the same reason (number disconnected, template not approved, invalid destination, outside the service window). Read errorMessage on GET /v1/messages/{id} and resend once the cause is fixed, rather than resending blindly in a loop.

Finding what failed — it differs by connection type

An official (Meta Cloud API) number receives no canonical message.* event at all. Not message.failed, not message.sent — none of the family, whatever the cause of the failure. On those numbers your webhook receives Meta’s native envelope, so a failure arrives as value.statuses[].status: "failed" with an errors[] array.
Changed on 2026-09-17. Until that date message.sent and message.failed did reach webhooks on official numbers that had subscribed to "*". They no longer do. If your receiver keys off those two events on an official number, switch it to the native envelope’s value.statuses[] or to the list endpoint below.The canonical families that do reach an official number are number.*, call.* and flow.response_received.
On an unofficial number nothing changes: message.failed is dispatched as before. The discovery path that works for both connection types is GET /v1/messages?status=FAILED. The message row reaches FAILED either way — including for a failure Meta itself reported — so the list and this endpoint agree regardless of how the number is connected.

Headers

Requires the messages:resend permission — ADMIN and OWNER. This is deliberately higher than messages:send: a resend spends quota on a send you already paid for once, and it can deliver twice.

Path parameter

Body

None. This endpoint accepts no body fields: the message comes from the URL and the content comes from the original send. Any field you send is refused with 400 UNKNOWN_FIELDS, naming it. There is no whatsappInstanceId override here — a resend always leaves on the number the key points at.

Example

Response (202)

originalMessageId is the message you resent. Everything else is the new message, in the same shape POST /v1/messages/send returns — including flowToken, when the template carries a Flow button. The resend mints a new flowToken, so the answer that comes back can be attributed to this attempt and not to the first one.

Common errors

Plus everything POST /v1/messages/send can answer, unchanged: a resend refused for no quota reads exactly like a send refused for no quota.

Why SENT and QUEUED are refused

SENT does not mean settled. It means an acknowledgement arrived; the delivery may still land, and the platform is already watching that message at 30 and 60 minutes and will re-dispatch it itself if it never reached the provider. Resending on top of that gives one message two senders. QUEUED is still being handled by the worker and the reconciler. In both cases the answer names the current status in details.status, so you can decide what to do without a second call.