# AsyncAPI

AsyncAPI describes an interface where messages arrive rather than being requested: channels, the operations that send or receive on them, and the message envelope. Connect publishes one at `/developers/asyncapi.yaml` to **3.1.0**, covering outbound webhooks — one channel, one envelope, signed.

- **Status:** Reference
- **Audience:** developer
- **Last verified:** 2026-09-10
- **Canonical:** https://connectbyjbrh.com/docs/protocols/asyncapi/

## Why request/response description does not fit events

An OpenAPI operation is a question and an answer in one exchange. An event has neither shape: nobody asked, the receiver did not choose the moment, and the interesting facts are the envelope, the delivery guarantee and the signature rather than a status code. Describing a webhook as though it were a request the receiver serves gets you an accurate path and none of what a receiver actually needs.

AsyncAPI names the pieces directly. A **channel** is where messages flow. An **operation** says whether this application sends or receives on that channel. A **message** carries headers, a payload schema and examples. Version 3.1.0 makes the send/receive direction explicit rather than leaving a reader to infer it, which matters when the same document describes both sides of a system.

## Does Connect use AsyncAPI?

Yes, as a publisher, and the scope is deliberately one thing. The document describes **outbound webhooks**: Connect notifying a subscribing system that a conversation was answered, a follow-up fell due, or something needs a person. One channel, `workspaceEvents`, whose address is the HTTPS endpoint the workspace registered; one operation, `receiveEvent`, which Connect sends by HTTP POST; one message envelope.

| Part | Contents |
|---|---|
| Body | `id`, `type`, `occurredAt`, `workspaceId` and a `data` object |
| `X-Connect-Signature` | `sha256=` and the hex HMAC-SHA256 of `<timestamp>.<raw body>` |
| `X-Connect-Timestamp` | Unix seconds at signing — reject anything far from now |
| `X-Connect-Event` | The event type, mirrored from the body so a receiver can route without parsing |
| `X-Connect-Delivery` | Attempt identifier; changes on each retry, unlike `id` |

Delivery is at-least-once. The `id` is stable across retries and the delivery identifier is not, which is the pair a receiver needs: de-duplicate on `id`, log the delivery identifier when something goes wrong. A receiver that treats every POST as a new event will double-count the first time a network hiccup causes a retry.

What is **not** in this document is as deliberate as what is. Inbound provider callbacks — a carrier posting a call event, a mailbox push, a messaging webhook — are the application's own inbound surface. They are authenticated by provider signature, their shapes change with the provider, and they are not a developer API. Describing them here would invite integration against something nobody promised to keep stable.

## Verifying an event before you trust it

1. Read the raw body as bytes. Do not parse it yet.
   - Result: You still have exactly what was signed. Re-serialising JSON changes bytes and breaks the comparison.
2. Recompute the HMAC-SHA256 over `<timestamp>.<raw body>` with the subscription secret and compare in constant time.
   - Result: A mismatch means the request did not come from Connect, whatever the body claims.
3. Reject a timestamp more than a few minutes from now.
   - Result: Replay is bounded. A valid signature on an old body is still an old body.
4. Look up `id`. If you have seen it, acknowledge and stop.
   - Result: At-least-once delivery becomes effectively-once processing, which is the property your database actually needs.

> **Careful** Compare signatures in constant time. A plain equality check leaks timing information and turns a secret into something guessable, one byte at a time.

## When AsyncAPI is worth the file

- **More than one message type.** With a dozen event types and a shared envelope, a description is cheaper than a page of prose per type.
- **Receivers you do not control.** Somebody else's team needs the schema, the headers and the retry semantics in a form their tooling reads.
- **A schema that is generated.** If the envelope is derived from code, the description can be too, and the two cannot drift.
- **Not for a single internal callback.** One provider webhook with one shape does not need a specification file; it needs a sentence and a signature check.

Connect's document is on the first three counts and pointedly not on the fourth, which is why the internal provider callbacks are excluded.

## Questions

### Does AsyncAPI replace OpenAPI?

No; they describe different halves. [OpenAPI](/docs/protocols/openapi/) covers the calls a client makes; AsyncAPI covers the messages that arrive unasked. A system with both an API and webhooks publishes both, and Connect does.

### Is the webhook signature the same as the API's authentication?

No. API authentication proves who is calling Connect. A webhook signature proves that a request arriving at *your* endpoint came from Connect. They point in opposite directions and use different secrets — see [Verifying a Connect webhook signature](/developers/webhook-signatures/).

### Should I use webhooks or poll?

Webhooks when you need to react promptly and can host a public HTTPS endpoint that verifies a signature; polling when you cannot. The trade-off is set out in [Webhooks or polling](/developers/webhooks-vs-polling/).

## Related

- [OpenAPI](https://connectbyjbrh.com/docs/protocols/openapi/)
- [The AsyncAPI description](https://connectbyjbrh.com/developers/asyncapi-events/)
- [Outbound webhooks](https://connectbyjbrh.com/developers/webhooks-outbound/)
- [Verifying a Connect webhook signature](https://connectbyjbrh.com/developers/webhook-signatures/)
- [Webhooks or polling](https://connectbyjbrh.com/developers/webhooks-vs-polling/)

## What this page is based on

- https://www.asyncapi.com/docs/reference/specification/v3.1.0
- `webapp/developers/asyncapi.yaml` — the channel, operation and envelope
- `docs-source/facts.py` — PROTOCOLS['asyncapi'], re-verified 2026-09-10
