Workflow Builder

Inbound webhooks

Let an external system start a workflow by posting to a stable, signed URL

What inbound webhooks do

An inbound webhook gives a use case a stable public URL that an outside system can POST to in order to start an execution. GitHub, Stripe, a form tool, a monitoring system, or any service that can send an HTTP request becomes a trigger for your workflow.

It is the fifth trigger source, alongside manual, chat, schedule and API. See Triggers and execution.

Inbound and outbound webhooks point in opposite directions, and they are separate features.

An inbound webhook lets an external system start a workflow. An outbound webhook lets MagOneAI tell an external system that a run finished. They share a name and nothing else: different permissions, different configuration, different tables.

You can use both together for a full round trip. An outbound webhook can even filter on the inbound_webhook trigger type, so your system is notified exactly when a run it started has finished.

Inbound webhook or API trigger?

Both let an outside system start a run. Pick by who is calling.

API triggerInbound webhook
CallerYour own backendA third party you do not control
CredentialA MagOneAI API keyA secret the sender signs with, or a static header value
Input shapeYou send MagOneAI's input schemaThe sender sends its own payload, you map it
Who adaptsYour codeMagOneAI, through field mappings

If you cannot change what the sender sends, you want an inbound webhook.

One use case, several webhooks

A use case can carry several inbound webhooks, one per sender, up to 10 by default. A GitHub hook and a Stripe hook can feed the same workflow with completely different payload shapes.

Each webhook is fully independent. Each has its own:

  • URL token
  • Secret
  • Field mappings
  • Rate limit budget
  • Delivery log

One sender's retry cannot suppress another's request, and one sender's secret does not verify against another's token.

The rate limit budget is per sender, not per use case. Three webhooks give one use case three times the verified-request budget. That is intended, because they genuinely are three different senders. There is deliberately no use-case-wide counter, because one noisy sender would then throttle a quiet one.

Names do not have to be unique. Two webhooks called "Stripe", one for production and one for sandbox, is a normal setup. The UI tells them apart by token prefix and creation date.

Requirements

A delivery is mapped and validated against the published schema, not the draft, because the published version is what actually runs. Publish before you point a sender at the URL. See Versions and publishing.

A webhook can start a form-interface use case only. A chat or hybrid use case is refused with a 400 rather than dispatched, because a webhook has no resumable chat session to attach the run to.

Set up a webhook

Open the inbound webhooks section on the use case

In Studio, open the use case and find the Inbound webhooks section. If none exists yet, the panel prompts you to create the first one.

Choose an authentication mode

Set Auth mode. Two modes are available. See Authentication for how each one is verified.

  • HMAC SHA-256: the sender signs the raw request body. Use this whenever the sender supports it.
  • Shared secret: the sender sends a fixed value in a header. Use this only when the sender cannot sign.

For HMAC, set the signature header and prefix

Name the header the sender puts its signature in, and the prefix it puts in front of the hex digest, if any.

SenderHeaderPrefix
GitHubX-Hub-Signature-256sha256=
A custom senderwhatever it usesoften empty

If the prefix does not match, verification fails, so copy it exactly.

Copy the secret, once

MagOneAI generates a secret and shows it in plaintext exactly once, on creation and again on each rotation. It is never shown again.

Copy it into the sender's configuration now. If you lose it, rotate the secret and update the sender.

Copy the receive URL

The URL is the webhook token on a fixed path:

POST https://<your-magoneai-host>/api/v1/inbound/{webhook_token}

The token is a path identifier, not a credential, so the UI keeps it visible with a copy control. The secret is the credential.

Map the sender's payload to the Start node inputs

Add one row per field under Field mappings. See Field mapping.

Send a test delivery and read the delivery log

Trigger a real event on the sender's side, then open the delivery log on the webhook. Every request that got as far as signature verification is recorded there, successful or not, with the failure reason. See The delivery log.

Authentication

HMAC SHA-256

The sender computes an HMAC-SHA256 of the raw request body using the secret, hex-encodes it, and sends it in the header you configured.

X-Hub-Signature-256: sha256=<hex digest of the raw body>

MagOneAI signs the same bytes it read off the wire and compares in constant time.

There is no timestamp and no time window. Third-party senders such as GitHub and Stripe sign once with their own scheme and do not send a MagOneAI timestamp. Replay protection is handled by delivery idempotency instead, not by a freshness window. See Idempotency.

This is a deliberate difference from outbound webhooks, where MagOneAI is the sender and does include a timestamp.

Shared secret

The sender puts a fixed secret value in a header, and MagOneAI compares it in constant time.

A shared secret is weaker than HMAC. The value is identical on every request, so anything that logs or proxies the request sees a reusable credential, and nothing binds it to the body.

Use HMAC whenever the sender supports it. Reach for a shared secret only for a sender that cannot sign.

What an unauthenticated request gets

An unknown token, an inactive webhook, and a webhook whose secret cannot be decrypted all return 404. They deliberately look identical, so probing cannot tell a real token with a broken secret from a token that does not exist.

Field mapping

A mapping row says: take this path out of the sender's JSON, and put it in this Start node input field.

Each row has three parts:

PartMeaning
SourceA dot-path into the sender's payload, for example data.object.customer_email
TargetThe Start node input field to fill
Source typeHow to interpret the value: value, url, or filename

Source types

Valid against every input type except file, image and audio.

The value is coerced to the target's type, so a numeric string lands correctly in a number field. A url_image target also uses value, because a url_image field holds a URL string and nothing is fetched or stored for it.

Valid against a file, image or audio target, and against an array whose items are one of those types.

MagOneAI fetches the URL, checks it against SSRF rules and your allowed host list, validates the file's magic bytes, stores it, and extracts its text. Audio and video are transcribed. Only then does the execution start, with the resulting file IDs merged into its input.

For an array target, the sender sends a JSON array of URL strings:

{ "attachments": ["https://example.com/1.pdf", "https://example.com/2.pdf"] }

Each URL is fetched and processed independently, and the results are merged back in the array's original order.

Inline base64 is not supported. File, audio and video input is URL-fetch only.

Turning on file fetching

A webhook that maps any file field needs Accepts files turned on, and you should set Allowed fetch hosts to the hosts the sender's URLs actually come from. That host list is what stops a sender pointing MagOneAI at an arbitrary URL.

Idempotency

Senders retry. A webhook that starts a second execution on a retry would process the same event twice.

MagOneAI computes a delivery key per request and keeps it:

The Idempotency-Key header, if the sender sends one

This is the project's existing convention, and it is the most accurate option because the sender decides what counts as the same delivery.

Otherwise a hash of the request body

The body, not the signature header. In shared-secret mode the signature header is a static value, so hashing it would collapse every request from that sender into one bucket regardless of content.

A repeat delivery under a key already seen replays the original response: the same execution_id, no new execution, and no new delivery row.

Known limitation. Two genuinely different events with identical bodies and no Idempotency-Key header collapse into one delivery. If your sender can send a delivery id, configure it to send Idempotency-Key.

Responses

StatusMeaning
201A new execution was created. The response carries execution_id, poll_token and poll_url, and a Location header addressing the execution.
202A duplicate. The original execution's details are replayed.
400The payload could not be parsed, or the use case is chat or hybrid rather than form.
404Unknown token, inactive webhook, or a secret that could not be read.
413The body is larger than the cap.
422The mapped input failed validation against the published schema.
429A rate limit was hit, either per IP or per webhook.
503The execution was created but the workflow engine could not be reached. The response carries Retry-After.

On this route, 201 means new and 202 means replay. That is unusual, and it is the single most useful thing to know when debugging a sender's integration. If you see 202 where you expected work to happen, the sender is retrying a delivery MagOneAI already handled.

A sender that retries after a 503 is doing the right thing. MagOneAI deliberately clears the delivery key on a 503, so an identical retry is treated as new rather than being permanently stuck replaying a failed execution.

The delivery log

Every request that reaches signature verification writes a row, verified or not. Each row records whether the signature verified, the status code returned, the execution ID if one was created, the source IP, and the failure reason.

This log is the whole debugging story for a sender's integration. When a sender says "I posted and nothing happened", the log says whether the request arrived, whether it verified, and what MagOneAI did with it.

Rows are deleted after 30 days by default. Retention is not optional: an unauthenticated sender hammering a leaked token writes a row per attempt, so the table would otherwise grow without bound.

Limits

LimitDefaultWhat it protects
Per-IP rate limit120 per minuteApplied before the token is even looked up, so a flood writes no delivery rows
Per-webhook budget60 per 60 secondsCounted on verified requests only, so a leaked URL alone cannot exhaust a legitimate sender's budget
Max body size256 KBChecked on the declared length and again while reading
Max characters in an unbounded text field10,000Stops a sender smuggling a very large string into any text field that has no explicit length limit of its own
Webhooks per use case10Bounds how many public surfaces one use case exposes
Delivery log retention30 daysKeeps the table bounded

Files fetched in one delivery are additionally bounded by the platform's per-execution file limit, counted before any fetching starts so an oversized array is rejected early rather than after every file has been downloaded.

See Input limits.

Permissions

Two actions govern inbound webhooks, and they are separate from the outbound webhook actions:

ActionAllows
inbound_webhook.viewReading a webhook's configuration and its delivery log
inbound_webhook.manageCreating, updating, deleting, and rotating the secret or token

Permissions are scoped to the use case, so an inbound_webhook.manage grant covers every webhook on that use case. Per-webhook permissions are not expressible today.

The receive route itself is deliberately unauthenticated. The caller proves itself with its own signature, not with a MagOneAI session or key.

Rotation

Good patterns

Troubleshooting

Next steps

MagOneAI© 2026 Magure, Inc.

On this page