> ## Documentation Index
> Fetch the complete documentation index at: https://docs.methodfi.com/llms.txt
> Use this file to discover all available pages before exploring further.

# The webhook endpoint

Webhooks allow the Method API to notify your application
when certain events occur.

To receive Webhook notifications, create a Webhook by registering a
URL pointing to your application where triggered events should be
sent to. This URL is where Method will send event information
in an HTTPS POST request.

#### Handling webhooks

A Webhook event is considered successfully delivered when the
corresponding URL endpoint responds with a `2xx` HTTP status code
within 5 seconds.

If the criteria is not met, Method will reattempt 4 more times
with each new attempt being delayed according to an exponential backoff
algorithm, where the delay period between each attempt exponentially increases.

#### Handling duplicate deliveries

Webhook delivery is at-least-once: the same event can occasionally be delivered more than
once. This happens, for example, when your endpoint processes an event but does not respond
with a `2xx` within 5 seconds (the delivery is retried).

Every delivery includes a `method-webhook-delivery-id` header that uniquely identifies the
delivery. The value stays the same across retry attempts of the same event, so use it as an
idempotency key: record each processed ID and skip any delivery whose ID you have already
seen. As a general rule, we also recommend processing events idempotently: fetch the
resource referenced by the event and upsert your local state, so handling the same change
twice has no effect.

> [!NOTE]
>
> The `id` field in the request body is the ID of the resource that changed, not a unique
> event identifier; two different events for the same resource share it. Deduplicate using
> the `method-webhook-delivery-id` header, not the payload.

#### Automatic disabling

Method automatically changes a Webhook's status when a delivery fails 5 times (the
initial attempt and all retries) and more than 40% of the Webhook's delivery attempts
within the rolling 24-hour window have failed. The resulting status depends on your
team's grace period:

* If the Webhook is within a grace period configured for your team (measured from the
  Webhook's creation), its `status` is set to `requires_attention` and it continues to
  receive events.
* Otherwise, its `status` is set to `disabled` and no further events are delivered
  until the Webhook is reactivated.

In both cases an `error` is set on the Webhook object with details about the failure.

> [!WARNING]
>
> Monitor your Webhook endpoint's health to avoid exceeding the 40% failure threshold.
> Even intermittent failures can accumulate over a 24-hour window and trigger automatic
> disabling. We recommend responding with a `2xx` status code immediately and processing
> webhook data asynchronously.

To reactivate a disabled Webhook, use the [Update a Webhook](https://docs.methodfi.com/2026-03-30/reference/webhooks/update.md)
endpoint to set its `status` back to `active`. Before reactivating, ensure the underlying
issue has been resolved to prevent the Webhook from being disabled again.

#### Authentication

> [accordion] Webhook Request Validation
>
> We use the `auth_token` and `hmac_secret` you provide to enable webhook authentication.
> There are 2 application-layer ways to authenticate an incoming request, described below.
> Teams provisioned for webhook mTLS can additionally verify the delivery at the transport
> layer, using the client certificate Method presents during the TLS handshake. See
> [Mutual TLS for webhook delivery](#mutual-tls-for-webhook-delivery).
>
> > [accordion] 1. Authorization Header Verification
> >
> > The token for request validation will be sent as a `base64-encoded` string in the `Authorization` header of the webhook. You can verify the request originated from Method by `base64-encoding` your `auth_token` and comparing it to the incoming header value to ensure the integrity of the event.
> >
> > ```ts theme={null}
> > const SECRET = 'your-auth-token';
> > const expected_auth = Buffer.from(SECRET).toString('base64');
> > const incoming_auth = req.headers['authorization'];
> >
> > if (incoming_auth !== expected_auth) {
> >   return res.status(401).send('Invalid Authorization token');
> > }
> > ```
>
> > [accordion] 2. HMAC Verification
> >
> > If you provide an `hmac_secret` when registering your webhook, Method will include a `method-webhook-signature` header in every request. This is an `HMAC-SHA256` digest created using the `hmac_secret` as the shared secret, and computed over the string `timestamp:payload`, where `timestamp` is the value from the `method-webhook-timestamp` (UNIX timestamp in seconds) header (which is always included, even if no `hmac_secret` is provided) and `payload` is the raw request body.
> > Checking for the timestamp freshness (5 min window) is optional, but recommended.
> >
> > ```
> > ${method-webhook-timestamp}:${raw_payload}
> > ```
> >
> > The timestamp is always included in the `method-webhook-timestamp` header, even if no `hmac_secret` is provided.
> >
> > ```ts theme={null}
> > const HMAC_SECRET = 'your-auth-token'; // Replace with the hmac_secret you provided Method
> > const ts = req.headers['method-webhook-timestamp'];
> > const hmac_sig = req.headers['method-webhook-signature'];
> > const msg = `${ts}:${req.rawBody}`;
> > const expected_sig = crypto.createHmac('sha256', HMAC_SECRET).update(msg).digest('hex');
> >
> > if (!crypto.timingSafeEqual(Buffer.from(hmac_sig), Buffer.from(expected_sig))) {
> >   return res.status(401).send('Invalid signature');
> > }
> > ```
>
> > [accordion] Example: Minimal Express Server for Verifying Webhooks
> >
> > ```ts theme={null}
> > import express from 'express';
> > import crypto from 'crypto';
> > import bodyParser from 'body-parser';
> >
> > const app = express();
> >
> > const SECRET = 'your-auth-token';
> > const HMAC_SECRET = 'your-hmac-secret'; // Replace with the hmac_secret you provided Method
> > const expected_auth = Buffer.from(SECRET).toString('base64');
> >
> > app.use(bodyParser.json({
> >   verify: (req, _, buf) => req.rawBody = buf.toString(),
> > }));
> >
> > app.post('/webhook', (req, res) => {
> >   const ts = req.headers['method-webhook-timestamp'];
> >   const hmac_sig = req.headers['method-webhook-signature'];
> >   const incoming_auth = req.headers['authorization'];
> >
> >   if (!ts || !hmac_sig || !incoming_auth) return res.status(400).send('Missing headers');
> >   if (incoming_auth !== expected_auth) return res.status(401).send('Invalid Authorization token');
> >   // 1. check for freshness of the request
> >   if (Math.abs(Date.now() / 1000 - ts) > 300) return res.status(400).send('Timestamp expired');
> >
> >   // 2. message reconstruction
> >   const msg = `${ts}:${req.rawBody}`;
> >   // 3. compute HMAC 
> >   const expected_sig = crypto.createHmac('sha256', HMAC_SECRET).update(msg).digest('hex');
> >
> >   // 4. constant-time comparison
> >   if (!crypto.timingSafeEqual(Buffer.from(hmac_sig), Buffer.from(expected_sig))) {
> >     return res.status(401).send('Invalid signature');
> >   }
> >
> >   res.status(200).send('Accepted');
> > });
> >
> > app.listen(3000, () => console.log('Listening on port 3000'));
> > ```

#### Mutual TLS for webhook delivery

Method can present a client certificate during the TLS handshake when delivering
events to your endpoint. This lets your endpoint verify that the connection
originates from Method at the transport layer, in addition to the
`Authorization` header and HMAC signature checks described above.

> [!NOTE]
>
> Webhook mTLS is **optional and opt-in**, and must be provisioned per team. Teams
> that are not provisioned are unaffected: Method continues to deliver webhooks over
> standard TLS with no change to existing endpoints. To enable it, contact your
> Method representative.

Once your team is provisioned, configure your endpoint to request and verify the
client certificate Method presents. Your Method representative will supply the
certificate details and the issuing CA chain for your trust store. For client-certificate
authentication on requests your application makes *to* the Method API, see
[Mutual TLS (mTLS)](https://docs.methodfi.com/2026-03-30/reference/mtls.md).

## Webhook Objects

## Webhook event object

#### Webhook Event Types

| Name | Description |
| - | - |
| `account.create` | A new Account was successfully created. |
| `account.update` | An Account has been updated. |
| `account.opened` | An Account has been opened. |
| `account.closed` | An Account has been closed. |
| `sensitive.create` | A new Sensitive was successfully created. |
| `account.number.update` | An Account's number has been updated. |
| `sensitive.update` | A Sensitive has been updated. |
| `account_verification_session.create` | A new AccountVerificationSession was successfully created. |
| `account_verification_session.update` | An AccountVerificationSession has been updated. |
| `balance.create` | A new Balance was successfully created. |
| `balance.update` | A Balance has been updated. |
| `card_brand.create` | A new CardBrand was successfully created. |
| `card_brand.update` | A CardBrand has been updated. |
| `card_brand.available` | A CardBrand has been made available. |
| `connect.create` | A new Connect was successfully created. |
| `connect.update` | A Connect has been updated. |
| `connect.available` | An Async Connect has completed. |
| `credit_score.create` | A new CreditScore was successfully created. |
| `credit_score.update` | A CreditScore has been updated. |
| `credit_score.available` | A CreditScore has been made available. |
| `entity.create` | A new Entity was successfully created. |
| `entity.update` | An Entity has been updated. |
| `entity.new_accounts_pending_consent` | An Entity needs to consent new surfaced accounts. |
| `entity_verification_session.create` | A new EntityVerificationSession was successfully created. |
| `entity_verification_session.update` | An EntityVerificationSession has been updated. |
| `identity.create` | A new Identity was successfully created. |
| `identity.update` | An Identity has been updated. |
| `payment.create` | A new Payment was successfully created. |
| `payment.update` | A Payment has been updated. |
| `payment_instrument.create` | A new PaymentInstrument was successfully created. |
| `payment_instrument.update` | A PaymentInstrument has been updated. |
| `payment_reversal.create` | A new PaymentReversal was successfully created. |
| `payment_reversal.update` | A PaymentReversal has been updated. |
| `payoff.create` | A new Payoff was successfully created. |
| `payoff.update` | A Payoff has been updated. |
| `product.create` | A new Product was successfully created. |
| `product.update` | A Product has been updated. |
| `report.create` | A new Report was successfully created. |
| `report.update` | A Report has been updated. |
| `subscription.create` | A new Subscription was successfully created. |
| `subscription.update` | A Subscription has been updated. |
| `transaction.create` | A new Transaction was successfully created. |
| `transaction.update` | A Transaction has been updated. |
| `update.create` | A new Update was successfully created. |
| `update.update` | An Update has been updated. |
| `credit_score.increased` | A user's credit score has increased. |
| `credit_score.decreased` | A user's credit score has decreased. |
| `entity_attribute.create` | A new Entity Attribute was successfully created. |
| `entity_attribute.update` | An Entity Attribute has been updated. |
| `account_attribute.create` | A new Account Attribute was successfully created. |
| `account_attribute.update` | An Account Attribute has been updated. |
| `entity_attribute.credit_health_credit_card_usage_value.increased` | Credit card usage has increased. |
| `entity_attribute.credit_health_credit_card_usage_value.decreased` | Credit card usage has decreased. |
| `entity_attribute.credit_health_derogatory_marks_value.increased` | Number of derogatory marks increased. |
| `entity_attribute.credit_health_derogatory_marks_value.decreased` | Number of derogatory marks decreased. |
| `entity_attribute.credit_health_hard_inquiries_value.increased` | Number of hard inquiries increased. |
| `entity_attribute.credit_health_hard_inquiries_value.decreased` | Number of hard inquiries decreased. |
| `entity_attribute.credit_health_total_accounts_value.increased` | Total number of accounts increased. |
| `entity_attribute.credit_health_total_accounts_value.decreased` | Total number of accounts decreased. |
| `entity_attribute.credit_health_credit_age_value.increased` | Credit age has increased. |
| `entity_attribute.credit_health_credit_age_value.decreased` | Credit age has decreased. |
| `entity_attribute.credit_health_payment_history_value.increased` | Payment history score has improved. |
| `entity_attribute.credit_health_payment_history_value.decreased` | Payment history score has decreased. |
| `entity_attribute.credit_health_soft_inquiries_value.increased` | Number of soft inquiries increased. |
| `method_jwk.create` | A new Method JWK (public key) was successfully created. |
| `method_jwk.update` | A Method JWK has been updated (deprecated or disabled). |

```json THE WEBHOOK OBJECT theme={null}
{
  "id": "whk_cSGjA6d9N8y8R",
  "type": "account.update",
  "url": "https://reference.example.app/webhook",
  "metadata": null,
  "expand_event": false,
  "status": "active",
  "error": null,
  "created_at": "2020-12-09T00:41:05.647Z",
  "updated_at": "2020-12-09T00:41:05.647Z"
}
```

```json WEBHOOK EVENT OBJECT theme={null}
{
  "webhook_id": "whk_cSGjA6d9N8y8R",
  "id": "acc_BcRdHNjb9TYKV",
  "type": "account.update",
  "op": "update",
  "path": "/accounts/acc_BcRdHNjb9TYKV",
  "event": "evt_knqJgxKUnqDVJ",
  "refs": {
    "account": "acc_BcRdHNjb9TYKV",
    "entity": "ent_qKNBB68bfHGNA"
  }
}
```


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.