Outbound webhooks
Notify your own systems the moment a workflow execution finishes — with signed, retried deliveries you can trust
What outbound webhooks do
An outbound webhook lets MagOneAI push a notification to your system when a workflow execution finishes — succeeded or failed. Instead of polling for status, your backend receives an HTTP POST the moment a run ends, so you can update a record, kick off downstream work, or alert a channel.
This is the opposite direction from the API trigger and from inbound webhooks. Those let an external system start a workflow. Outbound webhooks let MagOneAI tell an external system that a workflow finished. If you want to launch a run from outside, use a trigger or an inbound webhook; if you want to be notified when a run ends, use an outbound webhook.
Receive and notify are separate features
The two webhook directions share a name and nothing else. They have their own configuration, their own tables and their own permissions.
| Inbound webhook | Outbound webhook | |
|---|---|---|
| Direction | A sender starts a run | MagOneAI reports a finished run |
| Lives on | One use case | A project subscription |
| What it does on receipt | Starts exactly one execution | Not applicable |
| Event list | None, there is only one action | Yes, you pick which events notify you |
| Permissions | inbound_webhook.view / inbound_webhook.manage | webhook.view / webhook.manage |
| Signature | The sender signs, MagOneAI verifies | MagOneAI signs, your endpoint verifies |
| Timestamp in the signature | No, replay is handled by idempotency | Yes |
Use both for a full round trip. Give the use case an inbound webhook so the sender can start a run, then add an outbound subscription filtered to the inbound_webhook trigger type, so the same system is told when its run finished.
How subscriptions work
You attach one or more webhook subscriptions to a use case. Each subscription has:
- A target URL — the HTTPS endpoint MagOneAI delivers to.
- A list of events it cares about.
- An optional trigger-type filter — only notify for runs started a certain way.
- A signing secret — used to sign every delivery so you can verify it came from MagOneAI.
A use case can have several subscriptions, so you can route workflow.failed to an alerting endpoint and workflow.completed to your application without them stepping on each other.
Events
| Event | Fires when |
|---|---|
workflow.completed | An execution finishes successfully |
workflow.failed | An execution ends in failure |
Subscribe to one or both per subscription.
Filtering by trigger type
A subscription can optionally filter which runs notify it by how the run was triggered: manual / in-app, chat, schedule, API, or inbound_webhook. Leave the filter empty to receive notifications for all trigger types.
Use a trigger-type filter to keep noise down. For example, notify only on schedule runs if you only care about your unattended nightly jobs, not the manual runs you launch from Studio while testing.
Set up a webhook
Open the use case's webhooks
Go to the use case you want to be notified about and open its webhook subscriptions.
Add a subscription
Enter the target URL MagOneAI should POST to, and choose the events (workflow.completed, workflow.failed, or both).
Optionally filter by trigger type
Pick the trigger types that should notify this subscription, or leave it empty for all of them.
Save the signing secret
On creation, MagOneAI shows the signing secret once. Copy it and store it securely (e.g. in your secret manager) — you'll need it to verify deliveries, and it isn't shown again.
Send a test event
Use Send test event to deliver a sample payload to your endpoint. Confirm your endpoint receives it, verifies the signature, and returns a 2xx before you rely on it.
Manage subscriptions from the use case: create, list, delete, send a test event, and view delivery history for each one.
The delivery payload
Each delivery is an HTTP POST with a JSON body describing the execution that finished. The exact fields may grow over time, so write your receiver to ignore unknown keys. A delivery looks roughly like this:
{
"event": "workflow.completed",
"execution_id": "exec_01HX...",
"use_case_id": "uc_01HX...",
"status": "completed",
"trigger_type": "schedule",
"output": {
"summary": "Processed 42 records"
}
}event—workflow.completedorworkflow.failed.execution_id— the run that finished; use it to drill into execution history.use_case_id— the use case the run belongs to.status— the terminal status of the execution.trigger_type— how the run was started (manual / in-app, chat, schedule, API).output— the workflow's output for completed runs.
Verifying deliveries
Every delivery is signed so your receiver can confirm it genuinely came from MagOneAI and wasn't tampered with. Each request carries these headers:
| Header | Contents |
|---|---|
X-MagOne-Signature | t=<unix_timestamp>,v1=<hex HMAC-SHA256> |
X-MagOne-Event | The event name, e.g. workflow.completed |
X-MagOne-Webhook-Id | The subscription that produced this delivery |
The signature is computed Stripe-style: take the timestamp t from the header, build the signed string as the timestamp, a literal ., then the raw request body, and compute an HMAC-SHA256 of that string keyed with your subscription's signing secret. Compare the resulting hex digest to v1.
signed_string = "<t>." + raw_request_body
v1 = HMAC_SHA256(signing_secret, signed_string) // compare to header, hexAlways verify the signature and check that the timestamp is recent before trusting a delivery. Compute the HMAC over the raw body (not a re-serialized copy), compare digests in constant time, and reject deliveries whose timestamp is too old — this is what prevents an attacker from replaying a captured request. Treat any delivery that fails verification as untrusted.
Reliability and delivery history
MagOneAI retries failed deliveries automatically, and records every attempt so you can see what happened.
If your endpoint doesn't return a success response, MagOneAI retries the delivery — up to 5 attempts with exponential backoff spread over roughly 30 minutes. Make your endpoint respond quickly with a 2xx once it has accepted the event; do the heavy work asynchronously so a slow handler doesn't cause needless retries.
Because deliveries are retried, your endpoint may receive the same event more than once. Use the execution_id (together with the event) to de-duplicate, so processing the same notification twice is safe.
Each attempt is logged against the subscription — including the response status code and the response body — so you can inspect delivery history when something isn't arriving. Use it to confirm test events landed, diagnose 4xx/5xx responses from your endpoint, and see whether a delivery eventually succeeded on retry.
A webhook URL that resolves to a private, loopback, link-local, reserved, or cloud-metadata address is refused. You will see this immediately at registration, as a validation error rather than a silent failure later.
The check runs again on every delivery attempt, not only when you save the subscription, and the connection is pinned to the address that was validated. That closes the case where a hostname resolves to a public address at registration and a private one afterwards. A delivery blocked this way is recorded as failed with an SSRF blocked error, and no response body is stored.
If your receiver lives on an internal network, put it behind a public HTTPS endpoint with its own authentication rather than pointing MagOneAI at the internal address.
Good patterns
- Test before you depend on it. Send a test event and confirm your endpoint verifies the signature and returns 2xx before wiring webhooks into anything important.
- Verify every delivery. Never act on a payload you haven't authenticated — check the signature and the timestamp first.
- Respond fast, process later. Acknowledge with a 2xx immediately and handle the work asynchronously to avoid retry storms.
- De-duplicate by
execution_id. Retries mean at-least-once delivery; idempotent handling keeps repeats harmless.