# Plaid Webhooks Receives Plaid webhook events at a Monad-hosted endpoint, optionally verifies each delivery's `Plaid-Verification` signature, filters to the webhook types you select, and emits the raw payload. **Sync Type: Push (Webhook)** ## Overview Plaid emits webhooks for the connections (Items) linked through it — lifecycle events (`ITEM`), plus product events like `AUTH`, `TRANSACTIONS`, and `IDENTITY`. This input is a **webhook receiver**: Plaid pushes events to a Monad-hosted endpoint rather than Monad polling an API. Each delivery is (optionally) cryptographically verified to confirm it genuinely came from Plaid, filtered to the webhook types you selected, and emitted with its **original payload preserved verbatim** — so no fields are dropped or reshaped, and new Plaid fields flow through automatically. :::info[Why a webhook, not a poll] Plaid has **no API to list or replay past webhook events**, and no endpoint to enumerate your Items. The webhook push is the only programmatic feed of these events. ::: ### Example: `ITEM` events `ITEM` webhooks are the connection-lifecycle/security audit trail and a common reason to use this input: | Webhook Code | Meaning | | --- | --- | | `ERROR` | An error occurred on the Item — most often the bank login stopped working (`ITEM_LOGIN_REQUIRED`) and the user must re-authenticate. | | `LOGIN_REPAIRED` | A previously broken login recovered without user action. | | `PENDING_EXPIRATION` | The user's consent for the Item expires within 7 days. | | `PENDING_DISCONNECT` | The Item is scheduled to be disconnected within 7 days. | | `USER_PERMISSION_REVOKED` | The end user revoked Plaid's access to the entire Item. | | `USER_ACCOUNT_REVOKED` | The end user revoked access to a specific account within the Item. | | `NEW_ACCOUNTS_AVAILABLE` | Plaid detected a new account the user could add. | | `WEBHOOK_UPDATE_ACKNOWLEDGED` | Confirmation that an Item's webhook URL was changed. | Other webhook types (`AUTH`, `TRANSACTIONS`, `IDENTITY`, …) are delivered to the same URL and can be selected via the **Webhook Types** setting. ## How It Works 1. Plaid delivers a webhook POST to the input's ingest URL. A single webhook URL receives **all** of an Item's webhooks across every type. 2. If **Verify Webhooks** is enabled (default), the input verifies each delivery's **`Plaid-Verification`** JWT (an `ES256` signature): it fetches Plaid's public verification key (`/webhook_verification_key/get`) using your **Client ID** and **Secret**, checks the signature, rejects webhooks older than 5 minutes (replay protection), and confirms the body's SHA-256 matches the signed value. Keys are cached, so this adds no per-event call to Plaid. 3. The input keeps deliveries whose `webhook_type` is in the configured **Webhook Types** (or all types if none are selected) and emits them; other types are acknowledged and dropped. Events are attributed by the **`item_id`** in each payload (and `account_id` for account-level events), so a single webhook URL serves your entire Item population — there is no need for a per-Item URL. :::warning[One webhook URL = one pipeline] Plaid sends **every** webhook type for an Item to the **one** URL you configure, and each Monad pipeline has its own ingest URL. So you **cannot** split event types across pipelines (e.g. an "ITEM pipeline" and an "AUTH pipeline") — Plaid would deliver everything to whichever single URL is set, and the other pipeline would receive nothing. To route different event types to different destinations, use **one** Plaid Webhooks input (leave **Webhook Types** empty to keep all) and **route by `webhook_type` downstream** to separate destinations. ::: ## Prerequisites Before configuring this input, you need: 1. **Plaid Production API credentials** — your **Client ID** and **Production Secret** (Plaid Dashboard → **Developers** → **Keys**), required when verification is enabled. They are used only to fetch Plaid's webhook verification keys; the input never pulls data from Plaid. 2. **The ability to set your Plaid webhook URL** — you configure the Monad ingest URL on your Items via Plaid's API (see Setup, Step 2). This is done in your own Plaid integration code. ## Setup ### Step 1: Create the input in Monad 1. Create a **Plaid Webhooks** input and add it to a pipeline. 2. Configure the **Webhook Types** you want (or leave empty for all), and — if **Verify Webhooks** is enabled — the **Client ID** and **Secret** (Production). 3. Copy the **Pipeline ID** from the pipeline details page. Your ingest URL is: `https://app.monad.com/api/v2/http/send/{pipeline_id}` ### Step 2: Point your Plaid Items at the ingest URL Plaid sets the webhook URL **per Item**, but you use the **same** Monad ingest URL for all of them: - **New Items** — set the `webhook` field in [`/link/token/create`](https://plaid.com/docs/api/link/#linktokencreate) to the ingest URL. Every Item created from that Link token inherits it. - **Existing Items** — call [`/item/webhook/update`](https://plaid.com/docs/api/items/#itemwebhookupdate) with each Item's `access_token` and the ingest URL. On a successful `/item/webhook/update`, Plaid fires a `WEBHOOK_UPDATE_ACKNOWLEDGED` event to the new URL — a built-in reachability check. :::tip[Use one URL for all Items] Do **not** create a separate URL per Item or per account. Monad distinguishes every event by the `item_id` (and `account_id`) inside the payload, so one shared ingest URL handles your entire Item population. ::: ## Configuration ### Settings | Setting | Type | Required | Default | Description | |---------|------|----------|---------|-------------| | Verify Webhooks | bool | No | `true` | Verify each delivery's `Plaid-Verification` signature (ES256) before ingesting. Strongly recommended. When disabled, the endpoint accepts any payload (still protected by the pipeline's Monad API key). | | Webhook Types | array | No | (all) | Which `webhook_type` values to emit (e.g. `ITEM`, `AUTH`). Leave empty to emit every type. | | Client ID | string | If verifying | - | Your Plaid API `client_id`. Used with the secret to fetch Plaid's webhook verification keys. | | Secret | secret | If verifying | - | Your Plaid **Production** API secret. Used with the client ID to fetch verification keys. Stored as a secret. | `Client ID` and `Secret` are required only when **Verify Webhooks** is enabled. This input targets **Production** only. ## Record Fields Each record is one webhook delivery, emitted as **Plaid's original payload verbatim**, plus a single added field: | Field | Description | | --- | --- | | `received_at` | RFC3339 timestamp added by Monad when the delivery was processed. Plaid does not include an event timestamp, so this is the best available event time. | | *(all Plaid fields)* | Every field from the original webhook is preserved unchanged — `webhook_type`, `webhook_code`, `item_id`, `environment`, and any code-specific fields (e.g. `error`, `consent_expiration_time`, `account_id`, `new_accounts`). | Because the payload is passed through untouched, records stay correct even as Plaid adds or changes webhook fields. ## Sample Record ```json { "webhook_type": "ITEM", "webhook_code": "ERROR", "item_id": "wz666MBjYWTp2PDzzggYHM6oWWmBb", "environment": "production", "error": { "error_type": "ITEM_ERROR", "error_code": "ITEM_LOGIN_REQUIRED", "error_message": "the login details of this item have changed (credentials, MFA, or required user action) and a user login is required to update this information.", "display_message": "The credentials were updated. Please reconnect your account.", "request_id": "m8MDnv9okwxFNBV" }, "received_at": "2026-06-18T09:14:02Z" } ``` ## Limitations - **No backfill.** Webhooks are forward-only and Plaid does not replay past events, so only events that occur **after** the webhook URL is configured are ingested. - **One URL = one pipeline.** You can't split webhook types across multiple pipelines (see the warning above); route by `webhook_type` downstream instead. - **No `user_id`.** Plaid webhooks do not include an end-user identifier. Records carry `item_id`; map it to a user in your own system if you need that association. - **Production only.** This input handles Production webhooks. Sandbox is not supported today. - **Dashboard-configured webhooks excluded.** Plaid sends `TRANSFER`, `MONITOR`, `IDENTITY_VERIFICATION`, and a few other webhook types to a URL configured in the Plaid Dashboard, not the one set via `/link/token/create`. Those do not arrive at this input. - **Delivery reliability.** Ingestion is at-least-once; Plaid retries deliveries that don't receive a success response. ## Troubleshooting ### 1. Deliveries rejected (401) — when verification is enabled - **Signature failure**: Confirm the **Client ID** and **Secret** are the correct **Production** credentials — verification fetches keys with them. A mismatched or non-Production secret will fail key retrieval and reject deliveries. - **Stale deliveries**: Webhooks older than 5 minutes are rejected as possible replays. Persistent rejections can indicate severe clock skew between Plaid and the receiver. - To rule out signature issues during setup, you can temporarily disable **Verify Webhooks** (not recommended for production). ### 2. No data - **Webhook URL not set**: Confirm your Items point at the Monad ingest URL via `/link/token/create` (new Items) or `/item/webhook/update` (existing Items). A successful update fires `WEBHOOK_UPDATE_ACKNOWLEDGED`. - **Types filtered out**: If **Webhook Types** is set, only those types are emitted. Widen the selection or leave it empty to keep all. - **No matching events yet**: Events fire only when something happens on a connection. An idle, healthy Item produces nothing. ### 3. Missing historical events - Expected — there is no backfill. Only events after the webhook URL is configured are captured. ## Related Articles - [Plaid Webhooks overview](https://plaid.com/docs/api/webhooks/) - [Plaid Item webhooks reference](https://plaid.com/docs/api/items/#webhooks) - [Webhook verification](https://plaid.com/docs/api/webhooks/webhook-verification/) - [`/link/token/create`](https://plaid.com/docs/api/link/#linktokencreate) - [`/item/webhook/update`](https://plaid.com/docs/api/items/#itemwebhookupdate) ## Sync frequency This is a push (receiver) input: Monad ingests records as the source sends them, so there is no polling interval. See [Input Sync Frequency](../../guides/sync-frequency) for details.