Webhooks

Any service can wake a waiting program by posting JSON to a mailbox's send URL. This page covers the request, idempotency, limits and signing.

Early access (test mode). Payments run in Stripe test mode and no card is charged. Accounts, limits and prices may change before launch.

The send URL

Opening a mailbox returns a send capability. The send URL is $ALGAL_API/m/<habitat>/<send capability>. Posting to it needs no API key: holding the capability is the permission. The receive capability that the program waits on cannot post (403).

Post a message (bash)
curl -fsS -X POST "$ALGAL_API/m/$HABITAT/$SEND" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: order-1042-paid" \
  -d '{"order":1042,"status":"paid"}'
# {"deliveryId":"sha256:…"}

Idempotency

Every post needs an Idempotency-Key header of 1 to 512 characters. The mailbox keeps one message per key:

  • Posting the same key with the same body again returns the same deliveryId and adds nothing.
  • Posting the same key with a different body is refused with 409, so a retry can never replace a message.

Use a key your sender already has, such as its own event id. Then a sender that retries after a timeout delivers the message once.

Limits and errors

The body must be JSON. A message can be up to 64,000 bytes, and a mailbox can set a lower limit when it is opened. Errors come back as {"error":{"code","message"}} with these statuses:

StatusCodeMeaning
400PARSE_FAILEDNo Idempotency-Key header, a key longer than 512 characters, or a body that is not JSON.
403FORBIDDENThe capability is not a send capability for this habitat, or the habitat is not active.
409CONFLICTThe Idempotency-Key was already used with a different body.
413BUDGET_EXHAUSTEDThe message is larger than the mailbox allows, or the mailbox is full.
429RATE_LIMITEDMore than 60 posts in a minute to one habitat. The response carries retry-after: 60.

GitHub, Stripe, Slack and other stock senders

Stock webhook senders do not send an Idempotency-Key header, and some do not send JSON. Put a small relay in front: it checks the sender's own signature, copies the sender's event id into Idempotency-Key, and posts the JSON to the send URL.

Keep the send URL private

The capability sits in the URL path, so anything that logs full URLs can record it: proxies, CI output, shell history. Treat the send URL like an API key. If one leaks, open a new mailbox and move the program to it.

Signing (HMAC)

Signed posts are planned: an HMAC-SHA256 over the raw request body, checked against a per-mailbox secret that is set once and never returned by the API. The header names and the signing setup will be documented here when signing ships. Until then, the capability in the URL is the only check on who can post.