Inbound webhooks
Start an automation when any service sends an HTTP request to its Webhook trigger URL, with optional HMAC signing and de-duplication.
A Webhook trigger gives an automation its own URL. Any system that can make an HTTP request (your backend, a form tool, a payment provider) can start the workflow by calling it. No API token is needed: the unguessable token in the URL is the credential, and you can add a signing secret on top.
Where to find it
Drag Webhook Trigger from the Triggers group of an automation project's node palette. To start runs from your own code with an API token instead, use the automation trigger API.
Webhook URL or API token?
| Webhook trigger URL | Automation trigger API | |
|---|---|---|
| Address | https://www.buni.ai/api/hooks/t/{endpointToken} | https://www.buni.ai/api/v1/orgs/{orgId}/automations/{projectId}/trigger |
| Credential | Token in the URL, optional signature | Authorization: Bearer bai_ext_... |
| Best for | Third-party services that only let you paste a URL | Your own code |
| Test and production | Separate URLs | runtimeStage field |
| Scheduling, callbacks | No | runAt, delaySeconds, webhookUrl |
Set up a Webhook trigger
Add the trigger
Open the automation, add Webhook Trigger from the Triggers group of the palette, and open it.
Copy the URLs
The Endpoint section shows two URLs:
- Test runs the draft workflow straight away. Use it while you build.
- Prod runs the published workflow. It answers
409until the workflow is published.
Both stay the same when you edit or republish. Treat them as secrets: anyone with the URL can start the workflow.
Choose methods and response
| Setting | Options |
|---|---|
| Allowed methods | POST by default. Add GET, PUT, PATCH or DELETE if the caller needs them. Other methods get 405. |
| Expected payload | Optional JSON example of what callers send. It powers the run panel's payload form. |
| Mode | Async (202) answers immediately. Sync (wait for result) waits for the run and returns its result. |
| Custom status code | Optional. Replaces the default status in the response. |
Add a signing secret (recommended)
Select Generate signing secret and copy the secret. It is shown once. From then on every request must be signed, and the trigger shows Signed instead of Unverified. Select Rotate signing secret to replace it; the old secret stops working at once.
Test it
Use Send test event in the trigger panel, or call the Test URL yourself. Recent deliveries lists each request and the run it started.
Call the URL
The request body becomes the workflow's input. JSON and form-encoded bodies are parsed; any other body arrives as rawBody text. Query string parameters arrive under __query.
curl -X POST "https://www.buni.ai/api/hooks/t/$ENDPOINT_TOKEN" \
-H "Content-Type: application/json" \
-H "x-idempotency-key: order-12345-created" \
-d '{"orderId": "ord_12345", "amount": 4900, "currency": "USD"}'Sign requests
Once the trigger has a secret, send x-buni-signature with every request:
x-buni-signature: sha256=<hex HMAC-SHA256 of the raw body, keyed with the secret>Bare hex without sha256= is also accepted. Sign the exact bytes you send.
For replay protection, also send x-buni-timestamp with the current Unix time in seconds. The signature must then cover <timestamp>.<raw body>, and requests more than 5 minutes older or newer than the server's clock are refused.
BODY='{"orderId":"ord_12345","amount":4900}'
TS=$(date +%s)
SIG=$(printf '%s' "$TS.$BODY" | openssl dgst -sha256 -hmac "$BUNI_HOOK_SECRET" -hex | sed 's/^.* //')
curl -X POST "https://www.buni.ai/api/hooks/t/$ENDPOINT_TOKEN" \
-H "Content-Type: application/json" \
-H "x-buni-timestamp: $TS" \
-H "x-buni-signature: sha256=$SIG" \
--data-raw "$BODY"Without a timestamp, sign only the raw body: HMAC-SHA256(secret, body).
De-duplication
Send x-idempotency-key (up to 128 characters). A repeated key is acknowledged with 200 {"deduplicated": true, "eventId": "..."} and does not start another run. Without the header, two identical requests both run.
Responses
| Status | Body | Meaning |
|---|---|---|
202 | {"accepted": true, "invocationId": "..."} | Accepted in Async (202) mode. |
200 | {"invocationId": "...", "status": "SUCCEEDED"} | Sync mode, run succeeded. |
500 | {"invocationId": "...", "status": "FAILED", "error": "..."} | Sync mode, run failed. |
200 | {"deduplicated": true, "eventId": "..."} | Repeat of an earlier x-idempotency-key. Nothing ran. |
200 | {"ignored": true, "event": "..."} | App triggers only: an event type the trigger did not select. |
401 | Signature verification failed | Missing or wrong x-buni-signature, or a timestamp outside 5 minutes. |
404 | Unknown trigger endpoint | No trigger has this token. Copy the URL again from the trigger's Endpoint section. |
405 | Method GET not allowed for this trigger | Method not in Allowed methods. The Allow header lists the allowed ones. |
409 | This trigger is paused | The trigger is paused. |
409 | The workflow is not published; use the test URL or publish it | Prod URL called before publishing. |
410 | This trigger was removed from its workflow... | The trigger node was deleted. Re-add it, or use the new trigger's URL. |
413 | Payload exceeds the 1048576-byte limit | Body over 1 MB. |
429 | Rate limit exceeded | Too many requests. See Limits. |
A Custom status code replaces 202, 200 or 500 above.
Limits
- Body size: 1 MB.
- Each URL accepts 120 requests per minute by default, and all Webhook trigger URLs in your organization together accept five times that. Over the limit you get
429withX-RateLimit-Limit,X-RateLimit-RemainingandX-RateLimit-Reset. - Runs started from the URL count toward your plan's monthly API call quota. See Rate limits and quotas.