This site is being rebuilt and some pages are out of date. For current details, write to reach@inorbit.hr. This notice goes away when the rebuild is done.

No analytics unless you allow it, no tracking. This site keeps in your browser the language you pick, the theme, its colour, which site you chose, the currency on the pricing page and that you closed this notice; signing in adds session cookies. The legal page has the details.

Sign in

← Back to Platform

RFC 0022decided2026-10-02

Webhooks and MQTT

The platform tells you when something happens instead of waiting to be asked, as signed webhooks to your own endpoint or over MQTT, from one catalogue of events that carry ids and never content; managed in the console with every delivery on record.

Problem

The API answers when it is asked. Today it speaks REST, server-sent events, WebSocket, GraphQL, gRPC and MCP, and every one of them starts with the client: a request, a stream the client opens, a socket the client holds. Nothing reaches you if you are not already connected.

That is the wrong way round for most of what people build on a platform:

  • A team wants its own system to know the moment a token is revoked or a member leaves, without polling the API every minute.
  • An avatar or an automation needs to be woken by an event, not keep a connection open for days in case one comes (RFC 0017, RFC 0018).
  • Devices and services that live on unreliable networks (a sensor, a node's sidecar, a factory gateway) already speak MQTT and nothing else. They hold one light connection, survive drops, and subscribe to topics.

Webhooks are how the web answers the first two: Stripe, GitHub and Slack all deliver events by calling an address you give them. The way they go wrong is well known too: unsigned payloads anyone can forge, retries that become a flood, a delivery URL that points into the sender's own network, a payload that quietly carries personal data into a third party's logs, and no record of what was sent when something goes missing. The Standard Webhooks specification, steered by people from Zapier, Twilio, Svix, Kong, Supabase and ngrok, settles the signing and header shape (spec).

MQTT is an OASIS standard, at version 5.0 since 2019 (MQTT 5.0), and the protocol of the Internet of Things. Version 5 added what a gateway needs: reason codes on every refusal, request and response with a response topic and correlation data, limits the server announces at connect, and session expiry.

Proposal

One catalogue of events, three ways to receive it. Every service that has something to say publishes a typed event to one place. You receive the same event as a webhook at your endpoint, as a message on an MQTT topic, or on a server-sent events stream, and in each case it is the same JSON.

A diagram is drawn here in the RFCs product; this page does not show diagrams yet.

Events carry ids, never content

An event says what happened and to which object, and nothing about what was inside it:

{
  "id": "evt_01J9…",
  "type": "token.revoked",
  "occurred_at": "2026-10-02T14:03:11Z",
  "account_id": "acc_…",
  "data": { "token_id": "tok_…", "actor": "user_…" }
}

To learn more you ask the API with your own credentials, which checks that you may. Thin events keep personal data out of the logs of every system you forward them to, and a leaked event tells an attacker almost nothing. The first catalogue:

Type When
token.created, token.revoked an API token is made or revoked
key.created, key.revoked an API key is made or revoked
member.invited, member.joined, member.removed the team changes
usage.threshold the account's units cross 80% or 100% of its budget
radar.digest.published a new Radar digest is out
audit.event an entry is written to the account's audit log (added 2026-10-02)
webhook.test you pressed "send test event"

The catalogue is published over the API with a schema per type, and grows as services add events. A new type is a new schema, never a changed one.

Webhooks

You add an endpoint in the console's Webhooks section or over the API: an HTTPS address, a description and the event types it wants. Then:

  • Signed. Every delivery carries webhook-id, webhook-timestamp and webhook-signature, an HMAC-SHA256 over the id, the timestamp and the body, as the Standard Webhooks spec defines. The signing secret is shown once, when you create the endpoint or rotate the secret. During a rotation both secrets sign for a day, so you can switch without missing a delivery.
  • At least once, in the open. A delivery succeeds on any 2xx answer within a short timeout. Anything else is retried with growing gaps and some randomness: seconds, then minutes, then hours, for about a day and a half. The id stays the same across retries so you can drop duplicates. 410 Gone disables the endpoint at once; an endpoint that has failed every delivery for days is disabled and the console says so.
  • Every attempt on record. The console shows each delivery: event, attempt, status code, how long it took, the next retry. You can resend one, or send a test event. We keep what happened, never what your server answered: a response body can carry your data, so it is not stored.
  • The address cannot point inward. An endpoint must be a public HTTPS address. It is resolved before every delivery, private, loopback, link-local and metadata addresses are refused, and the connection is held to the checked address, as the browsing tool does (RFC 0014). Redirects are not followed.
  • Bounded. Endpoints per account, deliveries in flight per endpoint and payload size are capped; the numbers are on the developer docs' limits page.

A diagram is drawn here in the RFCs product; this page does not show diagrams yet.

MQTT

The platform speaks MQTT 5 over a secure WebSocket on the API host, so it passes the same edge, the same token check and the same firewall rules as the rest of the API. A raw TCP listener for devices that cannot do WebSocket can come later, through the same gateway.

  • Subscribe to events. events/<type> delivers the catalogue's events for your account: events/token.revoked, or events/# for everything. Wildcards work as MQTT defines them.
  • Subscribe to any stream. Every API route that answers with server-sent events is also a topic: the path without its leading /v1/. What you would read from an /events route arrives as messages.
  • Call any method. Publishing to rpc/<service>/<method> with an MQTT 5 response topic and correlation data calls that method as you, and the answer, or the error envelope, is published to your response topic. The response topic must be under reply/<your client id>/.
  • The same rules as every other way in. The token is checked at the edge when the connection opens; the methods allowed are the ones REST allows you; the scopes are the same scopes. MQTT changes how you call, never what you may call.
  • Deliberately small. Quality of service 0 and 1; 2 is refused with its reason code. A clean session every time: no stored sessions, no retained messages, no wills. The server announces its limits at connect (packet size, subscriptions, messages in flight, keep-alive), and refuses beyond them with a reason code rather than dropping silently. A broker that stores messages for offline clients is a different product; for delivery while you are away, use a webhook.

Who may do what

New token scopes, deny by default like the rest (RFC 0016):

Scope Allows
events:read the event catalogue, the events stream and events/# over MQTT
webhooks:read listing endpoints and their deliveries
webhooks:write creating, changing, testing, rotating and deleting endpoints, resending deliveries

Webhooks belong to the account, so a team's endpoints are the team's. Every change to an endpoint and every delivery is recorded as who, what and outcome, never the payload or the address in the logs.

Where it lives

A new events service owns the catalogue, the endpoints, their sealed signing secrets, the deliveries and the retry schedule. Services publish to it; they never deliver themselves. The gateway translates MQTT and server-sent events to the events service and to every other service, and owns no logic, as it does for REST. Deleting an account deletes its endpoints and their delivery history; deliveries are kept for thirty days.

Inbound webhooks, events sent to us by other systems, are a connection (RFC 0018); this RFC is the outbound half.

Alternatives considered

Run an MQTT broker (EMQX, VerneMQ, rumqttd and the like) beside the platform. A broker stores sessions and retained messages, has its own users and its own access lists, and would become a second place where permissions live. A gateway that translates MQTT to the methods we already authorise keeps one source of truth. If people need a real broker later, it will sit behind the same gateway, not beside it.

A hosted webhook service (Svix, Hookdeck). Good products, and they would hold every signing secret and see every event; another sub-processor for every customer to list. The core is a table, a signer and a retry loop, which we can own.

Fat events with the whole object. Easier for the receiver, and the reason personal data ends up in third-party logs. Thin events cost the receiver one API call.

Only webhooks, no MQTT. Webhooks need a public address to receive at, which a device behind a mobile network does not have. MQTT reaches it over the connection it opens.

MQTT 3.1.1. Wider device support, but no reason codes, no request and response, no announced limits. Version 5 is what a gateway can be strict and clear with; 3.1.1 can be added if devices ask for it.

Decision

Decided. One catalogue of thin events; outbound webhooks signed per Standard Webhooks, retried in the open and managed in the console; MQTT 5 over WebSocket as a surface of the gateway for events, streams and calls, under the same tokens and scopes as the rest of the API; a new events service that owns delivery.

Publication

The developer docs gain Webhooks and MQTT pages next to REST, server-sent events, WebSocket and MCP, the event catalogue in the reference, the new scopes in the tokens page and the limits on the limits page. The console gains a Webhooks section with endpoints, secrets and the delivery log, and a live view of your account's events.

Status log

  • 2026-10-02: opened and decided, to give the platform a way to tell you when something happens, and as the outbound half of connections (RFC 0018).
  • 2026-10-07: Checked: webhooks and MQTT run in the events and protocol deployments; nothing since the last line changes the decision. Status unchanged.

← Back to Platform