# The AsyncAPI description

`/developers/asyncapi.yaml` is an AsyncAPI 3.1.0 document describing exactly one thing: outbound events Connect delivers by HTTP POST to a URL a workspace has registered. One channel, one message envelope, six event types, an HMAC signature over the raw body. Everything else that moves over a webhook in this product is an inbound provider callback and is not described there.

- **Status:** Available
- **Audience:** developer
- **Last verified:** 2026-09-10
- **Canonical:** https://connectbyjbrh.com/developers/asyncapi-events/

## What the document contains

It is generated by `tools/docs_machine.py` alongside the OpenAPI and Arazzo descriptions, so its `info.version` tracks the product version rather than being maintained by hand. The whole document is one channel, `workspaceEvents`, whose address is the subscriber's own URL, and one operation, `receiveEvent`, whose `action` is `send` — Connect is the sender, your endpoint is the receiver.

| Field | Value |
|---|---|
| `asyncapi` | 3.1.0 |
| `info.title` | Connect by JBRH outbound webhooks |
| `defaultContentType` | `application/json` |
| `servers.production.host` | `connectbyjbrh.com`, protocol `https` |
| Channels | One: `workspaceEvents`, address `/{subscriberUrl}` |
| Operations | One: `receiveEvent`, `action: send`, HTTP binding `POST` |
| Messages | One: `Event`, `contentType: application/json` |

One channel is a design decision, not an omission. A receiver that has to handle six event types on one URL writes one signature check and one dispatch table; a receiver with six URLs writes the signature check six times and gets one of them wrong.

## The envelope

The payload schema is `Event`, and five fields are required: `id`, `type`, `occurredAt`, `workspaceId` and `data`. Only `data` varies by type, and it is declared `additionalProperties: true` with an instruction attached — a receiver must ignore fields it does not recognise, because new ones can appear inside `data` without the envelope changing.

```json
{
  "id": "ev_01J8Z6QK9M",
  "type": "followup.due",
  "occurredAt": "2026-09-10T09:15:00Z",
  "workspaceId": "ws_example",
  "data": {
    "followUpId": "fu_01J8Z6QK9M",
    "channel": "phone",
    "reason": "Call back about the quotation",
    "dueAt": "2026-09-10T09:15:00Z"
  }
}
```

`type` is a closed enumeration of six values: `conversation.received`, `conversation.replied`, `followup.due`, `followup.completed`, `needsyou.created` and `call.ended`. They are the events a business acts on, not a log of everything the engine does. `workspaceId` is documented as "always your own" — a subscription is scoped to one workspace and cannot receive another's events.

> **Note** Delivery is **at-least-once**, and `id` is stable across retries. That pair is the whole contract for correctness on the receiving side: de-duplicate on `id`, and never assume a second POST with the same `id` is a second occurrence.

## The headers, and where the signature is specified

The message declares three required headers and one optional one. The signature is specified on the operation rather than in prose elsewhere, which means a code generator reading the document sees it.

**`X-Connect-Signature`** — `sha256=` followed by the hex HMAC-SHA256 of `<timestamp>.<raw body>`, keyed with the subscription secret.
**`X-Connect-Timestamp`** — Unix seconds at signing. Reject anything far from now — that is what bounds replay.
**`X-Connect-Event`** — The event type, mirrored from the body so a router can dispatch without parsing JSON.
**`X-Connect-Delivery`** — Optional. The delivery attempt identifier; it changes on each retry, so it is the wrong thing to de-duplicate on.

The exact computation, and three worked verifiers, are on [verifying a Connect webhook signature](/developers/webhook-signatures/).

## What is deliberately not in it

The document's own header comment draws the line: internal provider callbacks — a carrier posting a call event, a mail push notification, a WhatsApp webhook — are not described, and are not a developer API. They are the application's inbound surface. They authenticate by provider signature, their shapes are the provider's rather than Connect's, and they change when a provider changes them.

Reading them out of a network trace and building against them would produce a client that breaks on a provider migration nobody announced to you, because from Connect's side that migration is an implementation detail. If you need call or message events, the six types above are the supported surface.

> **Careful** The capability registry records the *description* — `asyncapi` — as available. It records no outbound delivery capability. Build a receiver against this contract if you want to; do not plan a system that depends on receiving these events today. [Outbound webhooks](/developers/webhooks-outbound/) says so in full.

## Questions

### Why is there only one channel?

Because there is one delivery mechanism: an HTTP POST to a URL a workspace registers. AsyncAPI models the channel as the address, and the address is yours, so `/{subscriberUrl}` with one parameter is an honest description. Six channels would imply six endpoints you can subscribe to separately, which is not what the system does.

### Can I generate a client from this document?

You can generate the receiving side — models for the envelope, a handler stub per event type, and the header expectations. There is nothing to generate on the sending side, because you never send an event to Connect; the operation's `action` is `send` from Connect's point of view.

### Does the document describe how a subscription is created?

No. It describes the wire contract for a delivery, not the administration of the subscription. The registration flow is not part of the published public API, and this corpus does not document a create-subscription endpoint because there is no public write endpoint at all.

## Related

- [Outbound webhooks](https://connectbyjbrh.com/developers/webhooks-outbound/)
- [Verifying a Connect webhook signature](https://connectbyjbrh.com/developers/webhook-signatures/)
- [AsyncAPI](https://connectbyjbrh.com/docs/protocols/asyncapi/)
- [The OpenAPI description](https://connectbyjbrh.com/developers/openapi-description/)
- [Webhooks or polling](https://connectbyjbrh.com/developers/webhooks-vs-polling/)

## What this page is based on

- `webapp/developers/asyncapi.yaml` — the generated document
- `docs-source/facts.py` — PROTOCOLS['asyncapi'], CAPABILITY_STATUS
- Connect capability registry (docs-source/facts.py)
