# Detecting authentication failures

## Which event do I need?

**`item/error`.** There is no separate event for an authentication problem: every
execution that finishes in an error state — including one that failed at the
login step — is delivered as `item/error`. The full event list is in
[Webhook](/docs/developer-tools/webhooks-ref#data-events); nothing else in it is
auth-specific.

Register it like any other event:

```http
POST https://api.pluggy.ai/webhooks
X-API-KEY: {apiKey}
Content-Type: application/json

{
  "url": "https://example.com/pluggy",
  "event": "item/error"
}
```

There is no per-product wildcard. If you want every Item event on one endpoint,
register `all` instead of one webhook per event.

## Telling the failures apart

The notification carries an `error` object, and its `code` is the same value the
Item exposes as `executionStatus`:

```json
{
  "event": "item/error",
  "eventId": "d876fd7c-e9bd-4c4c-bd46-cc96c62aac29",
  "itemId": "d161a74a-8bc8-4093-88de-724312969b0d",
  "error": {
    "code": "INVALID_CREDENTIALS",
    "message": "Invalid credentials"
  },
  "triggeredBy": "SYNC",
  "clientUserId": "your-user-id"
}
```

The codes that mean "the connection could not authenticate" are:

| `error.code` | What it means | What the end user has to do |
| --- | --- | --- |
| `INVALID_CREDENTIALS` | The credentials were rejected by the institution. | Re-enter credentials. The Item's `status` becomes `LOGIN_ERROR` and it is **no longer auto-synced** until new credentials are provided. |
| `INVALID_CREDENTIALS_MFA` | The second login step failed: wrong or expired MFA token. | Start a new connection attempt and submit a fresh token. |
| `USER_INPUT_TIMEOUT` | The MFA token was never submitted in time. | Start again and submit the token within the window. |
| `ALREADY_LOGGED_IN` | The institution refused a new session because one is already active. | Close the open session at the institution, then retry. |
| `ACCOUNT_LOCKED` | The account is locked at the institution. | Contact the institution to unlock it. |
| `ACCOUNT_CREDENTIALS_RESET` | The institution is forcing a credentials reset (expired password, new security policy). | Reset the password at the institution, then reconnect. |
| `ACCOUNT_NEEDS_ACTION` | The institution is blocking data collection until the user does something (accept new terms, complete a profile). | Resolve it at the institution, then retry. |
| `USER_AUTHORIZATION_NOT_GRANTED` | Device authorisation was not granted to the connector. | Authorise the device in the bank app and reconnect. |
| `USER_AUTHORIZATION_REVOKED` | The user revoked data sharing at the institution. | Re-consent — this creates a new connection. |
| `USER_NOT_SUPPORTED` | The kind of account the user is connecting is not supported for that connector. | Use a supported account type. |

Every value, including the non-authentication ones, is described in
[Item lifecycle](/docs/connections/item-lifecycle#final-states).

<Callout variant="info" title="`USER_AUTHORIZATION_PENDING` also arrives as `item/error`">
It is an **intermediate** state, not a failure: the user has to authorise on
their device or at the institution, and Pluggy resumes the collection by itself a
few minutes later. Treat this code as "waiting", not as "broken", or you will ask
the user to reconnect a connection that was about to succeed.
</Callout>

<Callout variant="warning" title="The `error` object can be absent">
When the failure happened inside Pluggy while storing already-collected data, the
`item/error` event still fires but carries no `error` object — that internal code
is not exposed on client-facing surfaces. Handle a missing `error` without
crashing, and read the Item's `executionStatus` with
[`GET /items/{id}`](/reference/items-retrieve) when you need the detail.
</Callout>

## What is *not* an error

Two events look like failures and are not:

- **`item/waiting_user_input`** — the connector is asking for an MFA token. The
  login succeeded; the execution is suspended until the token is sent with
  [`POST /items/{id}/mfa`](/reference/items-send-mfa).
- **`item/waiting_user_action`** — the user must act on their device (approve in
  the bank app, scan a QR code).

Neither ends the execution, so neither should trigger a "your bank connection
failed" message to your user.

## Getting the `itemId`

`itemId` is in the payload of every `item/*` event, and so is the `clientUserId`
you set when [creating the Connect Token](/docs/authentication) — those two
fields are all you need to map the failure to one of your users, with no extra
API call.

If you never stored the `itemId` in the first place, see
[I lost an itemId. How do I find it again?](/docs/get-started/faq) — listing
items is opt-in per team, so the reliable path is to persist the `itemId` from
the widget's `onSuccess` callback or the `item/created` webhook.

## Reacting to it

1. Look up your user through `clientUserId` (or your own `itemId` mapping).
2. If the code is one of the credential errors above, prompt the user to
   reconnect. Re-sending the same credentials to an Item in `LOGIN_ERROR` without
   the user re-entering them will fail again.
3. Update the existing Item rather than creating a new one — see
   [Updating an Item](/docs/connect-widget/updating-item). A new connection means
   a new `itemId` and a fresh history.

Answer the webhook with a `2xx` **within 10 seconds** and process asynchronously;
a slow handler counts as a failure and is retried. The retry schedule is in
[Webhook](/docs/developer-tools/webhooks-ref#handling-notifications).