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

# Webhooks

> Get a signed request from Redbark Advisor when a client joins, a checklist item is submitted, a document is uploaded and more.

A webhook endpoint is an HTTPS address on your side. When something happens in your firm, Advisor sends it a signed `POST` with the event.

## Add an endpoint

1. Open **Settings > Webhooks**.
2. Select **Add endpoint**.
3. Enter the URL and choose the events to send. **Select all** also sends event types we add later.
4. Copy the signing secret. We show it once.

You can also manage endpoints through the API. See the Webhooks group in the sidebar.

* Every member can see endpoints and deliveries.
* Only admins can add, change, turn off, delete, roll secrets, resend or send tests.
* A firm can have up to 16 endpoints.
* The URL must be `https`, at most 2048 characters, with no user name or password. Private and internal addresses are refused.
* An endpoint gets only events that happen after it was added.

Use **Send test** on the endpoint's page to send a `webhook.test` event straight away. It is tried once.

## Event types

| Type | When |
| - | - |
| `client.created` | A client was added |
| `client.invited` | Invite links were issued: an invite, a reminder or a copied link |
| `client.joined` | A person opened their portal for the first time |
| `checklist.sent` | A checklist was sent to a client |
| `checklist.item_submitted` | The client submitted an item |
| `checklist.item_changes_requested` | The firm sent an item back |
| `checklist.completed` | Every required item of a checklist is done |
| `document.uploaded` | A document was uploaded, by the firm or the client |
| `fact.accepted` | The firm accepted a fact, as read or corrected |
| `account.connected` | An account was added, from a bank connection or a document |
| `account.disconnected` | A connection ended, sharing stopped, or a document account lost its last balance |
| `message.created` | A message was posted in a client thread, including document comments and system lines |

`GET /event-types` returns the same list. The pages under **Webhook events** in the sidebar show each event's `data`.

Payloads hold ids, not details. They never carry a person's name, an email address, a message body, a note, a file name or an amount. The one exception is `client.created`, which carries the client's name. Read the full record through the API, for example `GET /clients/{client}`.

## Envelope

Every request body is one JSON object:

```json theme={null}
{
  "id": "adv_evt_9c0bJm1tQd2nK4wPz7Lr3a",
  "type": "checklist.item_submitted",
  "api_version": "2026-10-30",
  "created_at": "2026-10-30T03:12:44.120Z",
  "organization_id": "adv_org_rFBeHmyzaTdUsrNs9KcWnu",
  "data": {
    "client_id": "adv_cli_UKTvx5nzBn2SmefA1CbPKO",
    "checklist_id": "adv_chk_4ZtqgS0oX1m2Vb8nYc6Dw9",
    "item_id": "adv_itm_7FhK2pL0sQ9rT3vW5yZ1aB",
    "kind": "document"
  }
}
```

| Field | Meaning |
| - | - |
| `id` | The event id. The same on every retry |
| `type` | The event type |
| `api_version` | Changes only for a breaking change to the envelope or to an event's `data` |
| `created_at` | When the change happened, in UTC |
| `organization_id` | Your firm |
| `data` | The event's payload. Its shape depends on `type` |

Each request also carries these headers:

| Header | Value |
| - | - |
| `Content-Type` | `application/json` |
| `User-Agent` | `Redbark-Advisor-Webhooks/1.0` |
| `Redbark-Signature` | `t=<unix seconds>,v1=<hex>` |
| `Redbark-Event-Id` | The event id, as in the body |
| `Redbark-Event-Type` | The event type, as in the body |
| `Redbark-Delivery-Id` | The delivery id |

## Verify the signature

Check `Redbark-Signature` on every request before you trust it.

* The secret starts with `whsec_`. Use the whole string, prefix included, as the key.
* The signed text is the timestamp, a full stop, then the raw body: `<t>.<body>`.
* The signature is HMAC-SHA256 of that text, as lowercase hex.
* While a secret roll overlaps, the header has one `v1` for each active secret. Accept the request if any of them matches.
* Reject a timestamp more than 5 minutes from your clock. That stops replays.

Use the raw body exactly as it arrived. Parsing and re-serialising the JSON changes the bytes and breaks the signature.

```js theme={null}
import { createHmac, timingSafeEqual } from 'node:crypto'

export function verifyRedbarkSignature(rawBody, header, secret, toleranceSeconds = 300) {
  if (!header) return false
  let timestamp
  const signatures = []
  for (const part of header.split(',')) {
    const [key, value] = part.split('=')
    if (key === 't') timestamp = Number(value)
    if (key === 'v1') signatures.push(value)
  }
  if (!Number.isInteger(timestamp) || signatures.length === 0) return false
  if (Math.abs(Date.now() / 1000 - timestamp) > toleranceSeconds) return false
  const expected = createHmac('sha256', secret).update(`${timestamp}.${rawBody}`).digest()
  return signatures.some((signature) => {
    const given = Buffer.from(signature, 'hex')
    return given.length === expected.length && timingSafeEqual(given, expected)
  })
}
```

With Express, read the body with `express.raw({ type: 'application/json' })` and pass `req.body.toString('utf8')`.

## Respond quickly

Answer with any 2xx within 10 seconds. Do slow work after you answer. Anything else counts as a failure, including a redirect.

## Retries

A failed delivery is tried again, up to 8 attempts in all:

| Attempt | Wait before it |
| - | - |
| 1 | None |
| 2 | 1 minute |
| 3 | 5 minutes |
| 4 | 30 minutes |
| 5 | 2 hours |
| 6 | 6 hours |
| 7 | 12 hours |
| 8 | 24 hours |

* The last attempt is about 45 hours after the first. After it the delivery is marked failed.
* Every result that is not a 2xx is retried, including 4xx.
* Each attempt is signed again with a fresh timestamp.
* You can resend any delivery from the endpoint's page or with the API. A resend counts toward the 8 attempts and does not restart the schedule.
* The endpoint's page shows the last 30 days of deliveries, with each response and payload.

## Dedupe on id

Deliveries can arrive more than once and out of order. Store each event `id` you have handled and skip repeats. Use `created_at` to put events in order.

## Failing endpoints

An endpoint shows as **Failing** after 20 failed attempts in a row, or once its failures have gone on for 24 hours. One success clears it.

If an endpoint keeps failing for 3 days, Advisor turns it off and emails your admins. An admin can turn it back on. Events from while it was off are not sent.

## Roll the secret

Select **Roll secret** on the endpoint's page, or call `POST /webhook-endpoints/{endpoint}/roll-secret`. You get the new secret once.

For 24 hours, each request carries a signature for both the new and the old secret. Switch your receiver to the new secret within that time. After 24 hours only the new secret signs.


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