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

# Webhooks

> Receive real-time notifications when events happen on Lemma.

Webhooks let your platform receive HTTP callbacks when events occur in Lemma. Instead of polling our API, you register a URL and we send a `POST` request to it whenever a relevant event fires.

To configure a webhook endpoint, [contact the Lemma team](mailto:contact@getlemma.com) with the URL you'd like to receive events at. We'll share a signing secret for that endpoint at the same time. We're planning to build a self-serve UI in the near future to manage endpoint URLs directly from the dashboard.

## Events

### `permissions.updated`

Fired whenever an entity's permissions for your platform change. Use this to know when an entity connects with you and when they revoke access.

The payload contains the entity ID and the **complete** current set of permissions your API key holds for that entity. Each permission is an object with a `resource_group`, an `operation`, and a `resource_id`. The grant allows access to the whole resource group when `resource_id` is null. When `resource_id` is present, the grant is scoped to exactly that resource. It is not a delta: every event carries the full list, so you can replace any cached permissions wholesale.

An empty `permissions` array means access has been fully revoked.

```json theme={null}
{
  "entityId": "entity_k7mXp2vR9nTfBw4s",
  "permissions": [
    { "resource_group": "entities", "operation": "read", "resource_id": null },
    {
      "resource_group": "bank_accounts",
      "operation": "read",
      "resource_id": null
    },
    {
      "resource_group": "bank_accounts",
      "operation": "send_external_transfer",
      "resource_id": "account_Rv4nBt8xKw2pMh6s"
    },
    {
      "resource_group": "bank_accounts",
      "operation": "send_external_transfer",
      "resource_id": "account_Lp7mKx3nWv9bTh4d"
    },
    {
      "resource_group": "external_accounts",
      "operation": "read",
      "resource_id": null
    },
    {
      "resource_group": "external_accounts",
      "operation": "initiate_ach_pull",
      "resource_id": "external_account_Bx5nTm8hKw3pVd7c"
    }
  ]
}
```

Every permission is one of the `resource_group` / `operation` pairs below. When a permission can have a `resource_id`, that ID narrows the grant to a single resource in the group. For example, to only allow you to read transactions for a specific bank account. Some resource groups will never have `resource_id` as shown in the table below.

| `resource_group`    | `operation`              | Can have `resource_id` | Grants the ability to                                                |
| ------------------- | ------------------------ | ---------------------- | -------------------------------------------------------------------- |
| `entities`          | `read`                   | No                     | Read the entity's identifying information, such as its company name. |
| `bank_accounts`     | `read`                   | Yes                    | Read the entity's Lemma bank account details.                        |
| `bank_accounts`     | `send_external_transfer` | Yes                    | Send money from the entity's accounts to external accounts.          |
| `bank_accounts`     | `same_entity_transfer`   | Yes                    | Move money between the entity's own Lemma bank accounts.             |
| `bank_accounts`     | `cross_entity_transfer`  | Yes                    | Move money between the entity's Lemma accounts and another entity.   |
| `external_accounts` | `read`                   | Yes                    | Read the external accounts saved on the entity's account.            |
| `external_accounts` | `write`                  | No                     | Add new external accounts to the entity's account.                   |
| `external_accounts` | `archive`                | Yes                    | Remove external accounts from the entity's account.                  |
| `external_accounts` | `initiate_ach_pull`      | Yes                    | Pull funds from the entity's external accounts via ACH.              |
| `inbound_mail`      | `read`                   | No                     | Read lockbox mail sent to the entity's Lemma account.                |

## Verifying webhooks

Lemma signs every webhook using the [Standard Webhooks](https://www.standardwebhooks.com/) specification. Each request carries three headers:

* `webhook-id` — unique message identifier
* `webhook-timestamp` — Unix timestamp (in seconds) of when we sent it
* `webhook-signature` — the signature over the payload

Always verify the signature with the signing secret we shared for your endpoint before trusting a payload. The official [standardwebhooks](https://www.npmjs.com/package/standardwebhooks) TypeScript library handles this for you:

```ts theme={null}
import { Webhook } from "standardwebhooks";

const webhook = new Webhook(process.env.LEMMA_WEBHOOK_SECRET); // "whsec_..."

// `body` must be the raw, unparsed request body string.
const payload = webhook.verify(body, {
  "webhook-id": headers["webhook-id"],
  "webhook-timestamp": headers["webhook-timestamp"],
  "webhook-signature": headers["webhook-signature"],
}) as {
  entityId: string;
  permissions: {
    resource_group: string;
    operation: string;
    resource_id: string | null;
  }[];
};

// Verified and parsed:
// {
//   entityId: "entity_...",
//   permissions: [{ resource_group: "bank_accounts", operation: "read", resource_id: null }, ...]
// }
```

`verify` throws if the signature is invalid or the timestamp is outside the allowed tolerance.
