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

# Webhooks

> Subscribe to real-time events from your Journeybee workspace

# Webhooks

Webhooks deliver real-time notifications when data changes in your
Journeybee workspace. Use them to sync leads into your CRM, trigger a
Zap, notify an ops channel, or drive any downstream workflow.

<Note>
  Webhooks today run through the **connector subscription API** (below). A more
  developer-oriented flow — including raw-signature HMAC, retries, and a UI for
  managing endpoints — is planned for a later release.
</Note>

## Quick start

Subscribe your endpoint to an event:

```bash theme={null}
curl -X POST https://api.journeybee.io/v1/webhooks/subscriptions \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "integration_id": 42,
    "event": "leads.create",
    "url": "https://hooks.example.com/journeybee"
  }'
```

Response:

```json theme={null}
{
  "id": "42.leads.create.1713300000000",
  "integration_id": 42,
  "event": "leads.create",
  "url": "https://hooks.example.com/journeybee",
  "active": true,
  "message": "Webhook subscription created successfully"
}
```

Your endpoint will now receive a `POST` for every matching event.

## Supported events

| Event                     | Description                   |
| ------------------------- | ----------------------------- |
| `leads.create`            | A lead was created            |
| `leads.update`            | A lead was updated            |
| `leads.delete`            | A lead was deleted            |
| `leads.notes_create`      | A note was added to a lead    |
| `leads.notes_update`      | A lead note was updated       |
| `leads.notes_delete`      | A lead note was deleted       |
| `deals.create`            | A deal was created            |
| `deals.update`            | A deal was updated            |
| `deals.delete`            | A deal was deleted            |
| `partners.create`         | A partner was added           |
| `partners.update`         | A partner was updated         |
| `partners.contact_create` | A partner contact was created |
| `partners.contact_update` | A partner contact was updated |
| `payments.processing`     | A payout was initiated        |
| `payments.completed`      | A payout completed            |
| `payments.failed`         | A payout failed               |
| `mdf.payout_completed`    | An MDF claim payout completed |
| `mdf.payout_failed`       | An MDF claim payout failed    |

## Subscription API

### Create a subscription

```
POST /v1/webhooks/subscriptions
```

Body:

| Field            | Type    | Required | Description                                   |
| ---------------- | ------- | -------- | --------------------------------------------- |
| `integration_id` | integer | yes      | The integration this subscription attaches to |
| `event`          | string  | yes      | One of the supported events above             |
| `url`            | string  | yes      | The HTTPS endpoint to POST events to          |
| `description`    | string  | no       | Free-text note for your own records           |

The `integration_id` identifies which integration row the subscription
hangs off. For Zapier-style self-serve subscriptions, use the public
Journeybee connector integration ID. Ask
[support](https://support.journeybee.io) if you're unsure which
integration to use.

### Delete a subscription

```
POST /v1/webhooks/subscriptions
```

Send `DELETE` with the `hookUrl` body field set to the subscription
`id` returned at create time:

```bash theme={null}
curl -X DELETE https://api.journeybee.io/v1/webhooks/subscriptions \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "hookUrl": "42.leads.create.1713300000000" }'
```

## Event delivery

Every subscribed event generates a single `POST` to your URL with:

* **`Content-Type: application/json`** — the request body is a JSON
  payload described below.
* **`Authorization: Bearer <token>`** — a JWT signed with your
  integration's UUID as the secret. Verify this before processing the
  payload.

### Example payload — `leads.create`

```json theme={null}
{
  "lead": {
    "uuid": "b1a9d5c3-...",
    "created_at": "2026-04-16T09:12:00.000Z",
    "updated_at": "2026-04-16T09:12:00.000Z",
    "status": "new",
    "source": "form",
    "company_name": "Acme Corp",
    "email": "buyer@acme.test",
    "phone_number": "+1-555-0100",
    "first_name": "Jane",
    "last_name": "Buyer",
    "partnership_name": "Northwind Referrals",
    "partnership_type": "referral",
    "created_by_user_uuid": "8e7d...",
    "created_by_user_email": "rep@northwind.test",
    "created_by_user_first_name": "Riley",
    "created_by_user_last_name": "Rep",
    "custom_fields": [
      {
        "uuid": "cf-...",
        "label": "Region",
        "type": "select",
        "options": [
          { "id": 1, "label": "EMEA" },
          { "id": 2, "label": "AMER" }
        ],
        "value": "EMEA",
        "custom_field_value_uuid": "cfv-..."
      }
    ],
    "tags": [{ "uuid": "tag-...", "label": "Hot" }],
    "assigned_users": [
      {
        "uuid": "u-...",
        "email": "ae@acme.test",
        "first_name": "Alex",
        "last_name": "AE"
      }
    ],
    "assigned_contacts": []
  },
  "configuration": []
}
```

Other events follow the same pattern — the entity key matches the
resource (`deal`, `partner`, `contact`, etc.).

## Verifying requests

The `Authorization` header contains a JWT signed with HS256 using your
**integration UUID** as the shared secret. Decode and verify it to
confirm the request is genuine.

### Node.js (jsonwebtoken)

```javascript theme={null}
import jwt from "jsonwebtoken";

const INTEGRATION_UUID = process.env.JOURNEYBEE_INTEGRATION_UUID;

app.post("/webhook", (req, res) => {
  const header = req.headers.authorization ?? "";
  const token = header.startsWith("Bearer ") ? header.slice(7) : null;
  if (!token) return res.status(401).end();

  try {
    const claims = jwt.verify(token, INTEGRATION_UUID, {
      algorithms: ["HS256"],
    });
    // claims = { company_uuid, user_uuid, event_id, external_settings, api_key }
    processEvent(claims.event_id, req.body);
    res.status(200).end();
  } catch {
    res.status(401).end();
  }
});
```

### Token claims

| Claim               | Description                                                     |
| ------------------- | --------------------------------------------------------------- |
| `company_uuid`      | The Journeybee company the event belongs to                     |
| `user_uuid`         | The user that triggered the event (falls back to an admin user) |
| `event_id`          | The event name (e.g. `lead_created`, `deal_updated`)            |
| `api_key`           | Optional API key for callbacks into Journeybee                  |
| `external_settings` | Your stored integration settings, including `authorisation[]`   |

<Warning>
  Always verify the JWT before acting on a payload. The integration UUID is the
  shared secret — do not commit it to source control and do not expose it in
  client-side code.
</Warning>

## Delivery semantics

* **At-most-once**: the worker sends one POST per event. There are no
  automatic retries on 5xx responses today, so your endpoint must return
  2xx quickly and queue work asynchronously.
* **Timeouts**: slow endpoints (>5s) may be dropped. Return a 2xx as
  soon as you've persisted the event, then process it out of band.
* **Ordering**: events are not guaranteed to arrive in order. Use the
  `updated_at` field on the payload to reconcile.
* **Duplicates**: duplicates are rare but possible. Key your idempotency
  off the entity `uuid` plus `event_id`.

<Note>
  Retries, signed timestamps, and a delivery log are on the roadmap (Platform
  phase 15). If you need guaranteed delivery today, poll the relevant list
  endpoints on an interval and reconcile against your local state.
</Note>

## Troubleshooting

* **Receiving 401s?** Double-check that you're verifying with the
  integration UUID (not your API key) and using HS256.

* **Not receiving events?** Confirm the subscription exists by listing
  your current subscriptions:

  ```bash theme={null}
  curl https://api.journeybee.io/v1/webhooks/subscriptions \
    -H "Authorization: Bearer YOUR_API_KEY"
  ```

* **Want to test delivery?** Use a tool like
  [webhook.site](https://webhook.site) as the `url` and trigger an
  event (create a test lead, update a partner) from the app.

## Related

* [Authentication](/guides/authentication) — API key setup
* [Errors](/guides/errors) — error response shapes and codes
