> ## 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 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.
</Note>

#### 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.
</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 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).

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

#### 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.
</Note>

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)](/2026-03-30/reference/mtls).

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

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


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