# WhatsApp error 131026: Message undeliverable — fix

URL: https://wa.genuka.com/en/docs/errors/131026
Language: English

> WhatsApp error 131026 (Message Undeliverable): number not on WhatsApp, terms not accepted or app too old. How to diagnose it and what to do next.

WhatsApp error 131026 means Meta could not deliver the message to the recipient: the number is
not on WhatsApp, the person has not accepted the latest terms of service, or their app is too
old. Do not retry on WhatsApp. Check the number you stored, then reach the person through another
channel such as SMS or email.

## What does error 131026 mean?

Meta describes it this way
([Cloud API error codes](https://developers.facebook.com/documentation/business-messaging/whatsapp/support/error-codes#other-errors)):

> "Unable to deliver message. Reasons can include: The recipient phone number is not a WhatsApp
> phone number. Recipient has not accepted the new Terms of Service and Privacy Policy. Recipient
> using an old WhatsApp version."

Three possible causes, all on the recipient's side. For the third one, Meta lists the minimum app
versions:

| App                               | Minimum version |
| --------------------------------- | --------------- |
| Android                           | 2.21.15.15      |
| SMBA (WhatsApp Business, Android) | 2.21.15.15      |
| iOS                               | 2.21.170.4      |
| SMBI (WhatsApp Business, iOS)     | 2.21.170.4      |
| KaiOS                             | 2.2130.10       |
| Web                               | 2.2132.6        |

The title that comes with the code in the `message` field is "Message Undeliverable"
([360dialog's list](https://docs.360dialog.com/docs/support/api-error-message-list)). It names the
symptom, not the cause: the code and `error_data.details` are what tell you why.

## When does error 131026 happen?

* **The number has no WhatsApp account**: a landline, a switchboard number, a typo in a form.
* **The number is badly formatted**: without `+` and a country code, a local number can be read
  as belonging to another country and land on someone without WhatsApp. Always write `to` in
  international format, for example `+237690000001`.
* **The recipient's app is out of date**, or they have not accepted the new terms of service.

### Where do you see it in Genuka WA?

The most common case: Meta accepts the send, then reports the failure on the status webhook.

| Channel                                                         | What you get                                                                             |
| --------------------------------------------------------------- | ---------------------------------------------------------------------------------------- |
| `message_status` webhook                                        | `data.status: "failed"`, `data.errors[0].code: 131026`                                   |
| `POST /api/v1/messages` response, when Meta refuses on the call | `400`, `"error": "send_recipient_permanent"`, `meta.code: 131026`                        |
| Campaign                                                        | The recipient turns `failed`, with Meta's message in `errorMessage`; it is never retried |
| Dashboard, Inbox section                                        | The failed bubble; "See why" shows Meta's code and details                               |

```json title="message_status webhook received by your endpoint (excerpt)"
{
  "id": "dlv_2f8c1a...",
  "type": "message_status",
  "field": "messages",
  "connection_id": "cn77...",
  "data": {
    "id": "wamid.HBgLMjM3...",
    "status": "failed",
    "timestamp": "1791451867",
    "recipient_id": "237690000001",
    "errors": [
      {
        "code": 131026,
        "title": "…",
        "message": "…",
        "error_data": { "details": "…" },
        "href": "/documentation/business-messaging/whatsapp/support/error-codes"
      }
    ]
  }
}
```

`data` is Meta's status object, passed through untouched: `error_data.details` varies with the
case and may name the cause.

## How do I fix error 131026?

1. **Do not retry on WhatsApp.** The same send will fail the same way. Genuka WA classifies 131026 as
   `recipient_permanent` and never retries it, in a campaign or a single send.
2. **Check the number as you stored it**: country code, extra or missing digits, a number typed
   into a "landline" field.
3. **Reach the person through another channel**, SMS or email, as Meta recommends, and ask them to
   check three things: that they can message your WhatsApp Business number themselves, that they
   have accepted the latest terms (Meta points to **Settings > Help** and **Settings > Application
   information**, which show the prompt when needed), and that their app is up to date.
4. **Mark the contact as unreachable on WhatsApp** in your database, and only re-enable it when they
   write to you: an inbound message proves they can receive.

**Node.js**

```ts
// app/api/webhooks/whatsapp/route.ts
import { verifySignature } from "@genuka/whatsapp/webhooks";

export async function POST(request: Request) {
  const raw = await request.text();
  const signature = request.headers.get("x-genuka-signature");
  if (!(await verifySignature(process.env.WEBHOOK_SECRET!, raw, signature))) {
    return new Response("invalid signature", { status: 401 });
  }

  const event = JSON.parse(raw);
  if (event.type === "message_status" && event.data.status === "failed") {
    const codes: number[] = (event.data.errors ?? []).map((e: { code: number }) => e.code);
    if (codes.includes(131026)) {
      // Yours to write: the contact moves to SMS/email until their next inbound message.
      void markWhatsAppUnreachable(event.data.recipient_id);
    }
  }
  return new Response("ok");
}
```

**Python**

```python
import hashlib
import hmac
import json
import os
import time

from flask import Flask, request

app = Flask(__name__)


def verify(raw: bytes, header: str, secret: str, tolerance: int = 300) -> bool:
    parts = dict(part.split("=", 1) for part in header.split(",") if "=" in part)
    timestamp = int(parts.get("t", "0"))
    if abs(time.time() - timestamp) > tolerance:
        return False
    expected = hmac.new(secret.encode(), f"{timestamp}.".encode() + raw, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, parts.get("v1", ""))


@app.post("/webhooks/whatsapp")
def whatsapp_webhook():
    raw = request.get_data()
    if not verify(raw, request.headers.get("X-Genuka-Signature", ""), os.environ["WEBHOOK_SECRET"]):
        return "invalid signature", 401

    event = json.loads(raw)
    if event["type"] == "message_status" and event["data"]["status"] == "failed":
        codes = [error["code"] for error in event["data"].get("errors", [])]
        if 131026 in codes:
            mark_whatsapp_unreachable(event["data"]["recipient_id"])  # your function
    return "ok"
```

## How do I prevent error 131026?

* **Validate numbers at input**, in international format with a country code.
* **Get the contact to reach you on WhatsApp first** when you can, with a "Message us on
  WhatsApp" link: a number that has already written to you is a number that receives.
* **Listen to the `message_status` webhook** rather than the send response: a `200` means
  "accepted by Meta", not "delivered".
* **Keep a fallback channel** for critical messages such as login codes. The
  [WhatsApp OTP in Node.js guide](https://wa.genuka.com/en/docs/guides/whatsapp-otp-nodejs) plans for it.

## Related error codes

* [131050](https://wa.genuka.com/en/docs/errors/131050): the recipient does receive WhatsApp, but stopped your
  marketing messages.
* [131049](https://wa.genuka.com/en/docs/errors/131049): a marketing message held back by Meta for this recipient,
  to try again later.
* [131047](https://wa.genuka.com/en/docs/errors/131047): the 24-hour window is closed; a template will go through.

## FAQ

### Should I retry a send that failed with 131026?

Not on WhatsApp. Until the recipient's situation changes (WhatsApp installed, app updated, terms
accepted), the same send will fail.

### Why was the send response 200 when the message failed?

Because Meta accepted the request before attempting delivery. Meta states that its errors come
back in the response, by webhook, or both, and recommends watching both. A 131026 most often
arrives on the status webhook.

### How do I find the 131026 recipients of a campaign?

`GET /api/v1/campaigns/{id}/recipients?status=failed&limit=1000` lists failed recipients with
Meta's message, 200 by default and 1,000 at most, with no pagination. The numeric code arrives on
the `message_status` webhook: store it there if you want to filter by cause, and it is the only
complete list for a large campaign.

### Is a message that failed with 131026 billed?

Not by Meta, which only charges for a template message when it is delivered
([Meta, Pricing](https://developers.facebook.com/documentation/business-messaging/whatsapp/pricing)).
On the Genuka WA side, however, a send Meta accepted counts against your subscription quota even
if delivery fails afterwards: drop unreachable numbers from your next sends.

## Sources

* [Meta — Error codes](https://developers.facebook.com/documentation/business-messaging/whatsapp/support/error-codes)
* [Meta — Status webhook reference](https://developers.facebook.com/documentation/business-messaging/whatsapp/webhooks/reference/messages/status)
* [Meta — Pricing](https://developers.facebook.com/documentation/business-messaging/whatsapp/pricing)
* [360dialog — API error message list](https://docs.360dialog.com/docs/support/api-error-message-list)
