Skip to main content

Webhooks

Inbound webhook triggers — POST to a token URL and a deployed automation runs. Token handling, rotation, idempotency, and the response codes.

6 min read

A webhook lets an external system start a deployed automation by posting to a secret URL. It suits services that can send an order event, form submission, or other notification to a fixed destination. A 202 response confirms acceptance and gives you a run ID; it does not confirm the automation has finished.

For a guided first setup, follow Trigger an automation via webhook. This reference covers the delivery contract, scope, token lifecycle, and retry behavior.

A worked trigger

Prepare a deployed automation

Save and deploy an automation whose tests pass. Bind a webhook in the editor, or send PUT /api/v1/automations/{name}/triggers with {"kind":"webhook"} using an authorized API key. Copy the newly issued token immediately; it is returned once.

Choose the URL from the automation's scope:

Work to startURLRequirement
Project run/api/projects/{id}/automations/webhook/{token}Active project in the token's organization, with this automation installed
Organization run/api/automations/webhook/{token}Automation with no project bindings

A project-bound automation refuses the global URL with 409 AUTOMATION_PROJECT_SCOPE_REQUIRED. Do not add a projectId query parameter: both URLs reject it with 400 INVALID_QUERY. A field inside the vendor body is input data, not a way to choose the project.

Send one delivery

Store the complete secret URL in TALE_WEBHOOK_URL using your sender's secret configuration. The following request uses a stable ID for one logical event:

bash
curl --fail-with-body --request POST "$TALE_WEBHOOK_URL" \
  --header 'Content-Type: application/json' \
  --header 'Idempotency-Key: order-12345-paid' \
  --data '{"orderId":"12345","status":"paid"}'

The accepted response has the shape {"runId":"..."} with HTTP 202. Save that ID alongside the sender's delivery ID. Tale passes the body to the automation inside this wrapper:

json
{
  "trigger": "webhook",
  "payload": { "orderId": "12345", "status": "paid" }
}

Read the order as input.payload.orderId. If the automation declares an inputs schema, it must describe this wrapper. A body that is not JSON becomes text in payload. The body limit is 256 KiB (262,144 bytes), measured as bytes arrive; larger requests receive 413.

Follow the result

Delivery scopeAuthenticated polling route
ProjectGET /api/v1/projects/{id}/runs/{runId}
OrganizationGET /api/v1/runs/{runId}

Poll with an API key whose holder can read that scope, or open the run in Tale. The webhook token starts deliveries; it is not a credential for reading REST results. Wait for a terminal run status before reporting that the work succeeded.

Interpret delivery responses

StatusCode/resultRecovery
202runIdAccepted; follow the run
202runId, duplicate: trueAlready accepted; follow the original run, no new run was created
400INVALID_QUERYRemove projectId from the query string
400AUTOMATION_INPUT_INVALIDCorrect the wrapper/schema mismatch using data.issues
403AUTOMATION_PROJECT_FORBIDDENCheck that the project is active, in the right organization, and has the automation installed
404Unknown, disabled, or mistyped token; also any non-POST methodCheck the saved URL and trigger state
409AUTOMATION_NOT_DEPLOYEDDeploy a version whose tests pass
409AUTOMATION_PROJECT_SCOPE_REQUIREDUse the installed project's URL
409AUTOMATION_DELIVERY_SCOPE_MISMATCHCheck the scope used for the original delivery ID
413Body too largeReduce the payload below 256 KiB
429Sender or trigger budget exhaustedWait at least Retry-After

A refused input creates no run. Project refusals deliberately do not distinguish a missing project, archived project, or missing installation, and do not reveal the automation's name. GET, HEAD, and OPTIONS all receive the same 404 as an invalid token, without an Allow header.

The token is the credential

The secret in the URL authorizes delivery. This endpoint does not verify a vendor HMAC signature or use an Authorization header. Keep the URL out of public issue reports, shared logs, and screenshots. Tale stores its hash and uses a constant-time comparison; plaintext is disclosed only when minted.

ChangeToken effect
PUT webhook with rotateToken: trueNew token returned once; old URL stops working immediately
Delete/unbind the triggerToken revoked; saved versions and run history remain
Replace webhook with schedule/eventToken revoked; response includes revoked: "webhook"
Bind webhook again after replacementNew token; the original does not return
Set enabled: falseURL suspended with 404, but token retained
Re-enable, including a later PUT that omits enabledThe same suspended URL becomes active again

Read revoked in scripted trigger changes so replacing a trigger type does not silently disconnect a partner that still uses the old URL.

Idempotency and retries

Deduplication applies to the trigger and the project in its URL. The same delivery ID can start one run in each installed project. Current project activity and installation are checked before Tale returns a cached duplicate.

IdentityDuplicate windowWhat must stay the same
Delivery-ID header24 hoursID value and scope; the body may differ and still counts as the same delivery
No delivery-ID header2 minutesByte-identical body and URL

For header-based identity, the first present header in this priority order wins:

text
Idempotency-Key
X-Idempotency-Key
webhook-id
X-GitHub-Delivery
X-Gitlab-Event-UUID
X-Shopify-Webhook-Id
Linear-Delivery
X-Atlassian-Webhook-Identifier
X-Request-UUID
I-Twilio-Idempotency-Token
X-Webhook-Id

Header names are alternatives, not separate namespaces. Forwarding a vendor's ID as Idempotency-Key preserves its identity. With no ID, JSON formatting changes the body bytes and may produce a new delivery. Prefer an explicit, stable event ID whenever your sender supports one.

Repeating the example request within 24 hours returns the original runId with duplicate: true. It does not rerun a failed automation. Decide separately how to recover a failed run instead of changing delivery IDs blindly.

Retry network failures and temporary 5xx responses with bounded exponential backoff. For 429, honor Retry-After. Keep the ID unchanged if a response may have been lost. Correct other 4xx causes before retrying. Deduplication prevents extra runs within its window; it does not guarantee exactly-once effects in an external service.

Budgets

BudgetRefillBurstCharged when
Sender IP, as reported by trusted proxies120/minute240Before token verification
Verified trigger20/minute40For delivery admission

Either budget can cause 429 with Retry-After in whole seconds and the normal error envelope. A token authenticates access to the trigger, but there is no separate sender-account identity to budget. Use the rate-limit guidance and keep the delivery ID stable while backing off.

Choose webhook or API key

Use a webhook when the sender supports a fixed event URL. Use an API key when your client must also discover automations, choose projects, or read results. Triggers explains setup in the app; the API reference describes authenticated starts and polling.

© 2026 Tale by Ruler GmbH — ISO 27001 & SOC 2 certified.

Tale is MIT licensed — free to use, modify, and distribute.