Webhooks
A webhook trigger exposes an automation at a fixed endpoint:
POST /automations/{id}
{id} is the automation's id. The endpoint is POST-only, so it never collides with your site's pages (which are served on GET).
Authentication
Set the trigger's auth field to one of:
none — public
No credentials required. Requests are rate-limited per IP to blunt abuse. Use this for trusted internal callers or low-risk endpoints, and keep the handler's side effects modest.
sameOrigin — browser forms from this site
Rate-limited like none, and the request must come from this site's own host. The browser's Origin header (falling back to Referer) must match the host the webhook was served on — otherwise the request is rejected with 403. A request with neither header is rejected.
This is the mode for a public-facing form on your own site that posts to an automation: your form is allowed, but another website's embedded JavaScript or form is not (a browser stamps a truthful Origin that page scripts cannot forge).
It is not a substitute for a key.
sameOriginis CSRF-grade: it stops browser requests from other origins, but a non-browser client (curl, a script, another server) can set anyOriginheader and bypass it. For anything sensitive, useapiKeyor validate a shared secret in the handler.
apiKey — protected
The request must carry a valid API key scoped to POST the /automations endpoint — the same method-and-path model as every other API key. Send the key in a header (never the body):
X-API-Key: tcms_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
or
Authorization: Bearer tcms_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Grant it when creating the key: in Utilities → API Keys → New, include POST in the methods, then under Specific Endpoints select All Automations (or grant all endpoints). A key that isn't scoped for POST /automations is rejected with 401. See API Keys.
Synchronous vs. asynchronous
The trigger's sync flag controls the response:
sync |
Behaviour | Response |
|---|---|---|
| on | The handler runs inline and the request blocks until it finishes. | 200 with the handler's return value as JSON. |
| off | The run is queued and processed on the next automations:process tick. |
202 Accepted with { "status": "queued", "runId": "…" }. |
Use sync when the caller needs the result (e.g. a lookup). Use async for fire-and-forget work, or anything slow — the caller isn't kept waiting and a long handler can't tie up a web worker.
The payload
The request's query string and parsed body are merged and handed to the handler as $ctx->args:
<?php
return function ($ctx) {
$orderId = $ctx->args['order_id'] ?? null;
// ...
return ['received' => $orderId];
};
curl -X POST https://example.com/automations/order-sync \
-H "X-API-Key: tcms_…" \
-H "Content-Type: application/json" \
-d '{"order_id": 42}'
The API key itself is read only from headers — it's never part of $ctx->args.
Server rewrite rules
POST /automations/{id} is a root-level route. If your server needs explicit rewrite rules (e.g. a Stacks install), make sure the automations path is routed to index.php — see Apache and Nginx.