> ## 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

export const get_common_parameters = (resource_name = 'resource') => {
  return {
    id: {
      name: 'id',
      type: "string",
      required: false,
      description: `Unique identifier for the ${resource_name}.`
    },
    entity_id: {
      name: 'entity_id',
      type: 'string',
      required: false,
      description: 'The ID of the associated Entity.'
    },
    account_id: {
      name: 'account_id',
      type: 'string',
      required: false,
      description: 'The ID of the associated Account.'
    },
    error: {
      name: 'error',
      type: 'object | null',
      required: false,
      description: <>
          An object representing an error that occurred while processing
          this {resource_name}. See <a href={`/reference/errors/${resource_name.replace(/^([A-Z])/, function (match) {
        return match.toLowerCase();
      }).replace(/([A-Z])/g, function (match) {
        return "-" + match.toLowerCase();
      })}-errors`}>{resource_name} errors</a>.
        </>
    },
    status_error: {
      name: 'status_error',
      type: 'object | null',
      required: false,
      description: <>
          An object representing an error that occurred while processing
          this {resource_name}. See <a href="/reference/errors/product-errors#status-errors">{resource_name} errors</a>.
        </>
    },
    metadata: {
      name: 'metadata',
      type: 'object | null',
      required: false,
      description: <>
          Additional data provided during creation.
          See <a href="/reference/metadata">metadata</a>
        </>
    },
    created_at: {
      name: 'created_at',
      type: 'string',
      required: false,
      description: `Timestamp of when the ${resource_name} was created.`
    },
    updated_at: {
      name: 'updated_at',
      type: 'string',
      required: false,
      description: `Timestamp of when the ${resource_name} was last updated.`
    },
    status: (enums = []) => ({
      name: 'status',
      type: 'enum',
      required: false,
      description: `Status of the ${resource_name}.`,
      enums
    }),
    type: (enums = []) => ({
      name: 'type',
      type: 'enum',
      required: false,
      description: `The type of ${resource_name}.`,
      enums
    })
  };
};

export const ParamList = ({items = [], is_child = false}) => {
  return items.map(item => {
    const field_props = {
      id: Math.random().toString(),
      body: item.name,
      name: item.name,
      type: item.type,
      required: item.required
    };
    const enums = item.enums || [];
    const items = item.items || [];
    const has_items = items?.length > 0;
    const has_enums = enums?.length > 0;
    const should_default_open = item.defaultOpen || false;
    const render_child_item = () => {
      const child_props = {
        title: has_enums ? "Possible enum values" : "properties"
      };
      if (should_default_open) child_props.defaultOpen = true;
      const has_inline_enums = has_enums && enums.every(enum_item => typeof enum_item === 'string') && enums.map((enum_item, idx) => {
        const is_last = idx === enums.length - 1;
        const is_2nd_to_last = idx === enums.length - 2;
        return <>
            <code>{enum_item}</code>
            {is_last && ''}
            {is_2nd_to_last && ' or '}
            {!is_last && !is_2nd_to_last && ', '}
          </>;
      });
      const enum_list = has_enums && !has_inline_enums && <Accordion {...child_props}>
          {enums.map((enum_item, index) => <div key={`enum-${index}`}>
              <code>{enum_item.name}</code>
              <br />
              <p>{enum_item.description}</p>
            </div>)}
        </Accordion>;
      const item_list = has_items && <Expandable {...child_props}>
          <ParamList items={items || []} is_child />
        </Expandable>;
      return <>
          <p>
            {item.description}
            {has_inline_enums && [has_inline_enums.length > 1 ? ' One of ' : ' Must be ', ...has_inline_enums]}
          </p>

          {enum_list}
          {item_list}
        </>;
    };
    return is_child ? <ResponseField {...field_props}>{render_child_item()}</ResponseField> : <ParamField {...field_props}>{render_child_item()}</ParamField>;
  });
};

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 an HTTP status code of 200
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.

#### Automatic disabling

Method will automatically disable a Webhook under either of the following conditions:

* **Consecutive failures:** If a Webhook delivery fails 5 times consecutively (after all retry attempts), the Webhook will be disabled.
* **High failure rate:** If 40% or more of Webhook deliveries fail within a rolling 24-hour period, the Webhook will be disabled.

When a Webhook is disabled, its `status` will be set to `disabled` and an `error` will be
set on the Webhook object with details about the failure. No further events will be
delivered until the Webhook is reactivated.

<Warning>
  Monitor your Webhook endpoint's health to avoid hitting the 40% failure threshold.
  Even intermittent failures can accumulate over a 24-hour window and trigger automatic
  disabling. We recommend responding with a `200` status code immediately and processing
  webhook data asynchronously.
</Warning>

To reactivate a disabled Webhook, use the [Update a Webhook](/2026-03-30/reference/webhooks/update)
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 title="Webhook Request Validation">
  We use the `auth_token` and `hmac_secret` you provide to enable webhook authentication.
  There are 2 ways to authenticate an incoming request.

  <AccordionGroup>
    <Accordion title="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>

    <Accordion title="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>

    <Accordion title="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'));
      ```
    </Accordion>
  </AccordionGroup>
</Accordion>

## Webhook Objects

<ParamList
  items={[
get_common_parameters("Webhook").id,
{
  name: "type",
  type: "enum",
  description: (
    <>
      The event type to be sent to this URL. See{" "}
      <a href="/2026-03-30/reference/webhooks/overview#webhook-event-types">Webhook Event Types.</a>
    </>
  ),
},
{
  name: "url",
  type: "string",
  description: "The URL receiving the webhook event.",
},
get_common_parameters("Webhook").metadata,
{
  name: 'expand_event',
  type: 'boolean',
  description: (
    <>
      Whether to expand the event object in the Webhook payload. (default: false)
    </>
  ),
},
{
  name: "status",
  type: "enum",
  description: 'The current status of the Webhook.',
  enums: [
    {
      name: 'active',
      description: 'The Webhook is active and will receive events.',
    },
    {
      name: 'disabled',
      description: 'The Webhook is temporarily disabled and will not receive events.',
    },
    {
      name: 'requires_attention',
      description: 'The Webhook has entered a state where it requires attention and will continue to receive events.',
    },
  ],
},
get_common_parameters("Webhook").error,
get_common_parameters("Webhook").created_at,
get_common_parameters("Webhook").updated_at,
]}
/>

## Webhook event object

<ParamList
  items={[
{
  name: "webhook_id",
  type: "string",
  description: "The unique identifier of the webhook subscription that triggered this event.",
},
{
  name: "id",
  type: "string",
  description: "The identifier of the resource modified.",
},
{
  name: "type",
  type: "string",
  description: (
    <>
      The event type. See{" "}
      <a href="/2026-03-30/reference/webhooks/overview#webhook-event-types">Webhook Event Types.</a>
    </>
  ),
},
{
  name: "op",
  type: "string",
  description: "The operation type performed on the resource (e.g., 'create', 'update').",
},
{
  name: "path",
  type: "string",
  description: (
    <>
      The URL path pointing to the affected resource. Make a HTTP GET
      request to this path to retrieve the resource.
    </>
  ),
},
{
  name: "event",
  type: "string",
  description: "The unique identifier of the event record. Can be used to retrieve the full event details via the Events API.",
},
{
  name: "refs",
  type: "object",
  description: (
    <>
      An object containing reference IDs to related resources. Commonly includes <code>entity</code> (the entity ID associated with the event) and may include other resource references like <code>account</code> depending on the event type.
    </>
  ),
},
]}
/>

#### 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.                            |
| `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_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).    |

<RequestExample>
  ```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"
  }
  ```
</RequestExample>

<ResponseExample>
  ```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"
    }
  }
  ```
</ResponseExample>
