# WhatsApp error 131047: Re-engagement message — fix

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

> WhatsApp error 131047 (Re-engagement message) means the 24-hour window is closed. Why Meta refuses the free-form message and how to switch to a template.

WhatsApp error 131047 means more than 24 hours have passed since the recipient last messaged you:
the customer service window is closed, and only an approved template can still reach them. The
fix is simple: resend the content as a template, then go back to free-form messages as soon as
the customer replies.

## What does error 131047 mean?

> "More than 24 hours have passed since the recipient last replied to the sender number."
> — [Meta, Cloud API error codes](https://developers.facebook.com/documentation/business-messaging/whatsapp/support/error-codes#other-errors)

Meta's suggested fix is one line: "Send the recipient a template message instead." In the
response's `message` field, the code is followed by its title, "Re-engagement message"
([360dialog's list](https://docs.360dialog.com/docs/support/api-error-message-list)); Meta warns
these titles will be deprecated, so do not code against them.

The rule behind the code is the **customer service window**. When a user messages or calls you, a
24-hour timer starts; each new message or call from them resets it to 24 hours. While it runs you
can send any free-form message: text, image, buttons, list. Once it expires, only approved
templates go through
([Meta, Send messages](https://developers.facebook.com/documentation/business-messaging/whatsapp/messages/send-messages#customer-service-windows)).

## When does error 131047 happen?

* **A free-form message sent too late**: the support answer goes out the next morning, while the
  customer wrote at 8 a.m. the day before.
* **A first free-form message** to someone who never wrote to you: there is no window at all.
* **A Flow sent as an interactive message** outside the window: same rule, see
  [Flows in the library](https://wa.genuka.com/sdk/flows).
* **Blocking a user**: Meta only lets you block a number that messaged you in the last 24 hours
  ([Meta, Block users](https://developers.facebook.com/documentation/business-messaging/whatsapp/block-users)).
  `POST /api/v1/blocked-users` then reports the failure number by number, with code 131047 and the
  message "Failed to block due to re-engagement check failed", which we recorded on a live number
  on August 13, 2026.

### Why does the API answer 200 when the message never arrives?

This is the main trap with this code. On August 13, 2026, on a live number, we sent three
free-form texts outside the window: Meta answered **HTTP 200 with a valid `wamid`** for each, and
none was delivered. No 131047 on the call. A template sent to the same recipient right after
arrived immediately.

Depending on the case, the error comes back on the call, later on the status webhook, or not at
all. That is why Genuka WA checks the window on its side before every free-form message and says
so in the response, without blocking the send:

```json title="200 — POST /api/v1/messages outside the window"
{
  "data": {
    "messageId": "wamid.HBgLMjM3...",
    "warning": {
      "code": "outside_service_window",
      "message": "This contact last messaged you at 2026-10-06T08:12:44.000Z, more than 24 hours ago, so the service window is closed. Meta will most likely drop this message; send an approved template instead."
    }
  }
}
```

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

| Channel                                   | What you get                                                                                                                                                           |
| ----------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `200` response of `POST /api/v1/messages` | `data.warning.code`: `outside_service_window` (last inbound message more than 24 h ago) or `unverified_service_window` (no inbound message on record for this contact) |
| `400` response of `POST /api/v1/messages` | `"error": "send_needs_template"`, `meta.code: 131047`, when Meta refuses on the call                                                                                   |
| `message_status` webhook                  | `data.status: "failed"` and `data.errors[0].code: 131047`                                                                                                              |
| Dashboard, Inbox section                  | The failed bubble; "See why" shows Meta's code and details                                                                                                             |

The `warning` is never a refusal: our record of the window comes from the webhook feed, and an
outage on our side must not block a send Meta would have delivered. The decision is yours.

## How do I fix error 131047?

1. **Do not resend the free-form message.** It will fail exactly the same way. Genuka WA classifies
   131047 as `needs_template` and never retries it, in a single send or in a campaign.
2. **Pick an approved template** of the right kind: `UTILITY` for order or account information,
   `MARKETING` for a promotion. The list is at `GET /api/v1/templates?status=approved` for templates
   created through Genuka WA; those created in WhatsApp Manager are not in it, but send the same way.
3. **Send it with the `template` field** on the same endpoint, always passing `language`.
4. **Go back to free-form when the customer replies.** Their reply opens a new 24-hour window.

**curl**

```bash
curl -X POST https://wa.genuka.com/api/v1/messages \
  -H "Authorization: Bearer $GENUKA_WA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "connectionId": "con_1",
    "to": "+237690000001",
    "template": { "name": "order_update", "language": "en", "body": ["Awa", "ORD-1042"] }
  }'
# { "data": { "messageId": "wamid.HBg..." } }
```

**Node.js**

```ts
const API = "https://wa.genuka.com/api/v1";
const WINDOW_MS = 24 * 60 * 60 * 1000;

async function post(path: string, body: unknown) {
  const response = await fetch(`${API}${path}`, {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.GENUKA_WA_API_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify(body),
  });
  const json = await response.json();
  if (!response.ok) throw Object.assign(new Error(json.message ?? json.error), json);
  return json.data as { messageId: string; warning?: { code: string; message: string } };
}

// lastInboundAt: when this contact last wrote to you, kept up to date by your webhooks.
export async function notify(to: string, text: string, lastInboundAt: Date | null) {
  const windowOpen = lastInboundAt !== null && Date.now() - lastInboundAt.getTime() < WINDOW_MS;
  const template = {
    connectionId: "con_1",
    to,
    template: { name: "order_update", language: "en", body: ["Awa", "ORD-1042"] },
  };
  if (!windowOpen) return post("/messages", template);

  try {
    return await post("/messages", { connectionId: "con_1", to, text });
  } catch (error) {
    if ((error as { error?: string }).error === "send_needs_template") return post("/messages", template);
    throw error;
  }
}
```

**Python**

```python
import os
import requests

response = requests.post(
    "https://wa.genuka.com/api/v1/messages",
    headers={
        "Authorization": f"Bearer {os.environ['GENUKA_WA_API_KEY']}",
        "User-Agent": "acme-crm/1.0 (+https://example.com)",
    },
    json={
        "connectionId": "con_1",
        "to": "+237690000001",
        "template": {"name": "order_update", "language": "en", "body": ["Awa", "ORD-1042"]},
    },
    timeout=30,
)
response.raise_for_status()
print(response.json()["data"]["messageId"])
```

**PHP**

```php
<?php
$ch = curl_init("https://wa.genuka.com/api/v1/messages");
curl_setopt_array($ch, [
    CURLOPT_POST => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_USERAGENT => "acme-crm/1.0 (+https://example.com)",
    CURLOPT_HTTPHEADER => [
        "Authorization: Bearer " . getenv("GENUKA_WA_API_KEY"),
        "Content-Type: application/json",
    ],
    CURLOPT_POSTFIELDS => json_encode([
        "connectionId" => "con_1",
        "to" => "+237690000001",
        "template" => ["name" => "order_update", "language" => "en", "body" => ["Awa", "ORD-1042"]],
    ]),
]);
$json = json_decode(curl_exec($ch), true);
curl_close($ch);
echo $json["data"]["messageId"] ?? $json["error"];
```

If the free-form message already went out and a `message_status` webhook comes back `failed` with
code 131047, the same rule applies: resend the content as a template, once.

## How do I prevent error 131047?

* **Keep your own window clock.** Every inbound message reaches your webhook with the sender and a
  timestamp: store the latest one per contact and choose template or free-form **before**
  sending.
* **Read `data.warning`.** When it is present, assume the free-form message will probably not be
  delivered, and watch its status.
* **Use a template for anything you initiate**: order confirmation, appointment reminder, login
  code. The [order notifications guide](https://wa.genuka.com/en/docs/guides/order-notifications) is built on that
  principle.
* **Keep a re-engagement template** in the `UTILITY` category, short, inviting the customer to
  reply so the conversation reopens.

## Related error codes

* [132001](https://wa.genuka.com/en/docs/errors/132001): the template you picked to re-engage does not exist in that
  language or is not approved.
* [132000](https://wa.genuka.com/en/docs/errors/132000): the template is found, but the number of variables does not
  match.
* [131050](https://wa.genuka.com/en/docs/errors/131050): the recipient stopped marketing messages; a `MARKETING`
  template will not go through either.
* [131026](https://wa.genuka.com/en/docs/errors/131026): the recipient cannot receive any message, templates included.

## FAQ

### Do the 24 hours start from my last message or theirs?

Theirs. The window starts at the **customer's** last message or call; the messages you send do
not extend it.

### Does a template reopen the 24-hour window?

No, the customer's reply does. Meta spells it out for marketing templates: if the user responds, a
24-hour customer service window starts
([Meta, Per-user marketing limits](https://developers.facebook.com/documentation/business-messaging/whatsapp/templates/marketing-templates/per-user-limits)).

### Is a free-form message sent inside the window billed?

Meta marks it `free_customer_service` in the status webhook: a non-template message sent within a
customer service window is free, as is a `UTILITY` template sent within the window
([Meta, status webhook](https://developers.facebook.com/documentation/business-messaging/whatsapp/webhooks/reference/messages/status)).
Templates are billed by Meta directly to the client's WhatsApp Business account; Genuka WA adds no
markup.

### Can I block a number that never wrote to me?

No. Blocking requires an inbound message from the last 24 hours, otherwise Meta answers 131047
for that number. `POST /api/v1/blocked-users` returns `207` when some numbers went through, `422`
when none did.

## Sources

* [Meta — Error codes](https://developers.facebook.com/documentation/business-messaging/whatsapp/support/error-codes)
* [Meta — Send messages, customer service windows](https://developers.facebook.com/documentation/business-messaging/whatsapp/messages/send-messages#customer-service-windows)
* [Meta — Status webhook reference](https://developers.facebook.com/documentation/business-messaging/whatsapp/webhooks/reference/messages/status)
* [Meta — Per-user marketing template message limits](https://developers.facebook.com/documentation/business-messaging/whatsapp/templates/marketing-templates/per-user-limits)
* [Meta — Block users](https://developers.facebook.com/documentation/business-messaging/whatsapp/block-users)
* [360dialog — API error message list](https://docs.360dialog.com/docs/support/api-error-message-list)
