Developers

Webhooks

The inbound webhook contract: URLs, bearer and HMAC verification, the three response modes with their status codes, and capture semantics.

The developer-facing contract behind the webhook trigger. Each webhook step gets its own endpoint:

POST https://<your-instance>/webhooks/<webhook-id>

Authentication

Two verification methods, set on the step:

  • Bearer — MadMax mints the token; send it as Authorization: Bearer …. Rotating the token on the step invalidates the old one immediately.
  • HMAC — for senders that sign payloads. Formats: github (X-Hub-Signature-256), stripe (Stripe-Signature), or plain (hex digest of the body; custom header name supported).

Unauthenticated deliveries get 401 and never touch a workflow.

Response modes

mode behavior caller sees
immediate ack now, run async 202 {"ok": true, "execution_id": …}
last step block until the run ends 200 + the terminal step's output
respond step block until a respond to webhook step fires that step's status/headers/body; the rest of the run continues in background

Other codes worth handling: 404 — unknown id or the workflow isn't active (deliberately indistinguishable); 500 — the run failed before responding; 504 — the response timeout elapsed (the workflow keeps running; only the HTTP wait gave up).

Sample capture

While a webhook step is capturing, one authenticated delivery is accepted even if the workflow isn't active — it returns 202 {"ok": true, "captured": true} and records the payload shape without running anything.

App events (platform-routed)

Each event arrives normalized: payload.text, user, channel, channel_type, ts, thread_ts, mentioned (the bot was @-mentioned or DM'd) and bot_thread (a reply in a thread the bot is already part of — so a conversation continues without re-mentioning it). Gate on mentioned or bot_thread for an assistant.

The webhook step's App event source is a different transport with the same trigger: instead of a per-step URL, a published MadMax app (Slack first) receives every event for the connected account at one platform endpoint, POST /webhooks/inbound/:provider, and MadMax routes it to the owning workspace by account id (team_id for Slack). Nothing is exposed per workflow, so there is no URL or secret to manage.

Each provider delivery is signature-verified with the app's own secret before parsing (fail-closed), deduplicated by the provider's event id, and filtered through a bot-loop guard so a bot can never trigger itself. The same endpoint family serves the platform SMS and email triggers (/inbound/surge, /inbound/resend).

Delivery semantics

MadMax doesn't re-request a failed inbound delivery — retries are the sender's job (most webhook senders retry on non-2xx). Once a delivery is accepted (202), execution retries happen internally. The endpoint is rate-limited per source IP.