# WhatsApp error 190: access token expired — cause and fix

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

> WhatsApp Cloud API error 190 (access token expired): which token expired, how to replace it for good, and why Genuka WA users never handle Meta tokens.

WhatsApp error 190 means the access token sent to Meta's Cloud API has expired or is no longer
valid. The fix is a new token, and above all to stop using a temporary user token: a system user
token does not die after a few hours. With Genuka WA, you never handle a Meta token at all.

## What does error 190 mean?

Meta lists it among the Cloud API authorization errors:

> "Your access token has expired." — suggested fix: "Get a new access token."
> — [Meta, Cloud API error codes](https://developers.facebook.com/documentation/business-messaging/whatsapp/support/error-codes#authorization-errors)

The general Graph API documentation, which the Cloud API is built on, names the same code "Access
token has expired" and documents subcodes
([Meta, Graph API error handling](https://developers.facebook.com/docs/graph-api/guides/error-handling)):

| Graph subcode | Meta's name          | What it tells you                                         |
| ------------- | -------------------- | --------------------------------------------------------- |
| `463`         | Expired              | The token expired, was revoked or is otherwise invalid    |
| `467`         | Invalid Access Token | The token expired, was revoked or is otherwise invalid    |
| `460`         | Password Changed     | The person who generated the token changed their password |
| `458`         | App Not Installed    | The user has not logged into your app                     |

Do not build logic on those subcodes: the WhatsApp error page says `error_subcode` is deprecated
and no longer returned from Graph v16.0 onward. The `190` code and the `details` field are enough.

## When does error 190 happen?

* **You are using the API Setup token.** Meta's App Dashboard generates a fresh user token every
  time you open **WhatsApp > API Setup**. Meta meant it for first tests: user tokens "expire
  quickly", so you have to generate a new one every few hours
  ([Meta, Access tokens](https://developers.facebook.com/documentation/business-messaging/whatsapp/access-tokens)).
  It is the usual reason a service works in the morning and fails in the afternoon.
* **The token was invalidated.** Password changed, app removed by the user, token revoked: Meta
  answers 190 even before the expiry date.
* **You are a partner and one customer's token died.** At the end of Embedded Signup, a Tech
  Provider receives a business integration system user token scoped to that customer. We have seen
  it stop working as soon as the customer edits the partner's access in their Business Manager:
  Meta then answers 190 "Authentication Error" on every send, for a number that is otherwise
  healthy.

## How do I fix error 190?

### If you call the Cloud API with your own token

1. **Inspect the token.** Paste it into
   [Meta's access token debugger](https://developers.facebook.com/tools/debug/accesstoken/): it shows
   the expiry and the permissions. It needs `whatsapp_business_management` and
   `whatsapp_business_messaging`
   ([Meta, WhatsApp support](https://developers.facebook.com/documentation/business-messaging/whatsapp/support#authentication-authorization)).
2. **Replace a user token with a system user token.** In Business settings, **System Users**: create
   a system user, give it control of your app, then **Generate token**, picking the app, an expiration
   preference and the `business_management`, `whatsapp_business_management` and
   `whatsapp_business_messaging` permissions
   ([Meta, Access tokens](https://developers.facebook.com/documentation/business-messaging/whatsapp/access-tokens#system-user-access-tokens)).
3. **Give it access to the accounts.** A system user without access to the target WhatsApp account
   no longer gets 190 but [200](https://wa.genuka.com/en/docs/errors/200): assign it the WhatsApp account and the Messaging
   account in Meta Business Suite, from each account's **People** tab (same page).
4. **Store it as an opaque secret.** Meta warns that token formats can change: no fixed column
   length, no decoding.

### If you go through Genuka WA

There is nothing for you to regenerate. Genuka WA holds the Cloud API access: every call on your
number goes out with Genuka's system user token, created with no expiry date, rather than with the
token Embedded Signup returned when the number was connected. Genuka made that choice precisely to
avoid the partner case described above.

A `190` in a Genuka WA response is therefore, in the vast majority of cases, an incident on
Genuka's side. It comes back like this:

```json title="409 — POST /api/v1/messages"
{
  "error": "send_config",
  "message": "Authentication Error",
  "meta": {
    "errorClass": "config",
    "retryable": false,
    "code": 190,
    "traceId": "AbC…"
  }
}
```

When creating a template, the same refusal comes back as `403 meta_rejected` with the same `meta`
object ([API reference](https://wa.genuka.com/en/docs/api#errors)). Either way:

1. **Do not retry in a loop.** `retryable: false`: the same call will fail the same way.
2. **Contact Genuka support** with the response's `x-request-id` header and `meta.traceId`.
3. **Do not ask the customer to reconnect straight away.** Check `GET /api/v1/connections` first.
   `disconnected` means Meta reported the account as no longer shared with Genuka, or deleted, or
   that you released the number yourself. A customer who removed access restores it through the
   [connect link](https://wa.genuka.com/en/docs/onboarding), without using a new slot; a released number needs a free
   slot again; a deleted account, or one disabled by Meta ([error 368](https://wa.genuka.com/en/docs/errors/368)), is
   not restored that way, and the `account_update` event forwarded to your webhooks tells you which
   case applies. If it still reads `connected`, stay with Genuka support.

Do not confuse it with errors on your own API key, which never come from Meta:
`401 missing_bearer_token` (no header) and `401 invalid_token` (unknown or revoked key), see
[Authentication](https://wa.genuka.com/en/docs/authentication).

**Node.js**

```ts
const response = await fetch("https://wa.genuka.com/api/v1/messages", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.GENUKA_WA_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    connectionId: "con_1",
    to: "+237690000001",
    template: { name: "order_confirmation", language: "en_US", variables: ["Awa", "ORD-1042"] },
  }),
});
const json = await response.json();

if (response.status === 401) {
  // Your Genuka key: missing, unknown or revoked. Nothing to do with Meta.
  throw new Error(`API key refused: ${json.error}`);
}
if (json.meta?.code === 190 || json.meta?.code === 0) {
  // Meta token refused: on Genuka's side. Stop and alert, do not retry.
  console.error("Meta token refused", {
    requestId: response.headers.get("x-request-id"),
    traceId: json.meta.traceId,
  });
}
```

**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']}"},
    json={
        "connectionId": "con_1",
        "to": "+237690000001",
        "template": {"name": "order_confirmation", "language": "en_US", "variables": ["Awa", "ORD-1042"]},
    },
    timeout=30,
)
body = response.json()

if response.status_code == 401:
    raise RuntimeError(f"API key refused: {body['error']}")  # your Genuka key, not Meta
meta = body.get("meta") or {}
if meta.get("code") in (0, 190):
    # Meta token refused: on Genuka's side. Alert, do not retry.
    print("Meta token refused", response.headers.get("x-request-id"), meta.get("traceId"))
```

**PHP**

```php
<?php
$ch = curl_init("https://wa.genuka.com/api/v1/messages");
curl_setopt_array($ch, [
    CURLOPT_POST => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HEADER => 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_confirmation", "language" => "en_US", "variables" => ["Awa", "ORD-1042"]],
    ]),
]);
$raw = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
$headerSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
curl_close($ch);

$body = json_decode(substr($raw, $headerSize), true);
preg_match('/x-request-id:\s*(\S+)/i', substr($raw, 0, $headerSize), $requestId);

if ($status === 401) {
    throw new RuntimeException("API key refused: " . $body["error"]); // your Genuka key
}
if (in_array($body["meta"]["code"] ?? null, [0, 190], true)) {
    error_log("Meta token refused, request " . ($requestId[1] ?? "?") . ", trace " . ($body["meta"]["traceId"] ?? "?"));
}
```

**curl**

```bash
curl -i -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_confirmation", "language": "en_US", "variables": ["Awa", "ORD-1042"] }
  }'
# -i prints x-request-id: include it, with meta.traceId, when you contact support.
```

## How do I prevent error 190?

* **Never run production on the API Setup token.** It exists to send a first test message, not to
  power a service that runs overnight.
* **One system user token per environment**, with only the WhatsApp permissions you need, kept in
  your secrets manager.
* **Watch for revoked access.** As a partner, the `account_update` webhook reports
  `PARTNER_APP_UNINSTALLED` when a customer deauthenticates or uninstalls your app, and
  `PARTNER_REMOVED` when an account is no longer shared with you
  ([Meta, account\_update webhook](https://developers.facebook.com/documentation/business-messaging/whatsapp/webhooks/reference/account_update)).
  Genuka WA forwards these events to your [webhooks](https://wa.genuka.com/en/docs/webhooks) (`account_update` event),
  but of the two, only `PARTNER_REMOVED` turns the number `disconnected`: after a
  `PARTNER_APP_UNINSTALLED`, it stays `connected`.
* **Alert on the class, not on the text.** In Genuka WA, `meta.errorClass: "config"` covers token,
  permission and registration problems: a human has to look, and no retry will fix it.

## Related error codes

* [0](https://wa.genuka.com/en/docs/errors/0): Meta could not authenticate the app user; same remedy.
* [200](https://wa.genuka.com/en/docs/errors/200): the token is valid but lacks access to the account or permission.
* [10](https://wa.genuka.com/en/docs/errors/10): permission not granted or removed.
* [3](https://wa.genuka.com/en/docs/errors/3): capability or permission missing for this endpoint.

## FAQ

### How long does a WhatsApp Cloud API access token last?

It depends on the type. A user token, like the one on the API Setup page, expires within a few
hours. A system user token is long-lived, and you choose its expiration preference when you
generate it.

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

Not until the token has changed. The same token produces the same error; retrying only delays the
alert.

### Does my customer need to reconnect after a 190 on Genuka WA?

Only if their number shows `disconnected` in `GET /api/v1/connections` because they removed
Genuka's access: the `account_update` event forwarded to your webhooks confirms it. A deleted
account, or one disabled by Meta, is not restored by reconnecting. If the number still reads
`connected`, contact Genuka support with `x-request-id` and `meta.traceId`.

### Should I regenerate my Genuka API key?

No. Your `pk_live_…` key authenticates your calls to Genuka WA, not to Meta. A refused key produces
a Genuka `401`, never a `190`.

## Sources

* [Meta — Error codes](https://developers.facebook.com/documentation/business-messaging/whatsapp/support/error-codes)
* [Meta — Access tokens](https://developers.facebook.com/documentation/business-messaging/whatsapp/access-tokens)
* [Meta — Graph API, Handling errors](https://developers.facebook.com/docs/graph-api/guides/error-handling)
* [Meta — WhatsApp support, authentication and authorization errors](https://developers.facebook.com/documentation/business-messaging/whatsapp/support#authentication-authorization)
* [Meta — account\_update webhook](https://developers.facebook.com/documentation/business-messaging/whatsapp/webhooks/reference/account_update)
