Skip to main content
Build and run

How to set up webhook triggers

A webhook-triggered automation runs whenever an external system sends an HTTP POST to the automation's unique URL. This is how you wire PSA/RMM alerts, monitoring tools, or automation platforms…

Written By Christopher Scaminaci

Last updated 3 days ago

A webhook-triggered automation runs whenever an external system sends an HTTP POST to the automation's unique URL. This is how you wire PSA/RMM alerts, monitoring tools, or automation platforms (Zapier, n8n, Power Automate) into a StackJack automation.

Set up a webhook automation

  1. Create an automation (or edit an existing one) and choose the Webhook trigger.
  2. Pick a Payload mode (how much of the sender's request body reaches the agent — see below).
  3. Optionally set Max payload size (bytes). Leave blank for the server default of 32 KB; the hard ceiling is 65,536 bytes (64 KB).
  4. Save, then open the automation detail page. The Webhook card shows everything the sending system needs.

The webhook URL is a credential

The webhook URL has the form:

https://agents.stackjack.io/webhooks/<automation-id>/<secret>

The secret is embedded in the URL itself — anyone who has the URL can trigger your automation. Treat it like a password:

  • Use the Copy button rather than transcribing it.
  • Don't paste it into tickets, chat, or documentation.
  • If it leaks, click Rotate secrets immediately.

Payload modes — how the body reaches your agent

ModeWhat the agent receives
None — discard bodyNothing from the request body. The run starts with the automation's own instructions only.
Full context — pass body verbatimThe entire request body is handed to the run as its trigger payload.
Selected fields — extract specific pathsOnly the JSON fields you list (simple dotted paths like ticket.id or alert.severity — no array indices or wildcards) are extracted and passed to the run.

Whatever payload survives filtering becomes the run's trigger payload — it is given to the agent as the context for that run, and you can see exactly what was received on the run's detail page in the Trigger payload card.

Payloads that exceed the size cap after filtering are rejected with HTTP 413 and no run starts.

Smart filters — starting a run only for the deliveries worth it

A smart filter is an optional judgement you add to a webhook trigger. Before any run starts, StackJack asks a specialist service a short question about the delivery and starts a run only when your conditions pass. Use it when the sender cannot narrow what it posts and you do not want a run — and its credits — for every message.

  • It judges every delivery that reaches it. There is no sampling. The filter runs after deduplication: if a delivery started a run, a byte-identical resend inside the 60-second deduplication window is answered as a duplicate and is not judged again. A resend of a delivery the filter skipped is judged again, and so is any resend to an automation without deduplication.
  • What you write. A provider and a model (typesafe and jev-latest; both are required), one or more questions of the same three types a decision step uses — Noul (yes/no), Choice or Score — and at least one condition under runWhen. When all of your conditions pass, the delivery starts a run. A filter with no condition is refused when you save.
  • It needs the payload. A filter judges the same filtered trigger payload the automation itself would receive, so an automation set to None — discard body cannot carry one; saving is refused and the editor offers to remove the filter. If a delivery arrives with no payload at all, it is not judged and the run starts.
  • When the service cannot answer, your If no answer can be reached choice decides: Run (the default — the delivery starts a run as though there were no filter) or Skip.
  • A skipped delivery is a success, not an error. The sender gets 200 with { "status": "filtered" }, so a well-behaved one does not retry it. No run is created and no run ID is returned, so the delivery costs no run credits. The filter's own judgement is a fast decision — see Fast Decisions and Your Own TypeSafe Key for what one costs. The response is deliberately the same whatever the filter decided — the sender is not the author. It is in the canonical webhook response table with every other status.
  • Steering attempts are flagged. StackJack flags payloads that try to steer the filter on the delivery's receipt, shown on the webhook receipts panel.
  • The whole judgement is bounded at two seconds, including any retry. It cannot hold up your sender.

Every judgement is recorded and shown on the automation's webhook receipts panel, with what was asked and what came back. If StackJack cannot read an automation's stored filter, the Automations page marks that automation Not Ready. Fix or remove the filter in the builder.

Running a webhook automation from your AI assistant

A webhook automation with Expose to external AI harness turned on can also be invoked manually from your connected AI assistant via stackjack_run_agent — without waiting for its webhook sender. The assistant passes the payload as the call's context; the run records as a manual run and the payload is fenced exactly like a real inbound webhook body. So the assistant knows what to send, stackjack_list_agents discloses the automation's payload mode, its selected-field paths, and the optional Example payload you can set on the webhook trigger editor (documentation only — incoming webhooks are never validated against it). To retry a previous delivery after fixing the automation, pass that run's id as rerunOfRunId and its stored trigger payload is replayed as a new run; the run detail page's Re-run with this payload button does the same thing.

Optional request signing (HMAC)

Beyond the URL secret, you can have senders prove the body wasn't tampered with:

  1. On the Webhook card, click Reveal next to Signing secret (it is masked by default) and copy it.

  2. Configure the sender to compute HMAC-SHA256(raw request body, signing secret) and send the resulting 32-byte digest as a header:

    X-StackJack-Signature: sha256=<hex digest>
    
  3. The header is optional — omit it and only the URL-secret check applies. If the header is present, an invalid signature is rejected with 401.

How the digest may be encoded

StackJack decodes the header back to the raw 32-byte digest before comparing, so the encoding your sender happens to use does not change the verdict. All of these are accepted:

Header valueWho sends it
64-character hexThe curl example on the Webhook card. Upper- and lower-case both work.
44-character base64HaloPSA — its signer emits bare base64, with no prefix.
base64url, padded or unpadded (43 or 44 characters)Senders that URL-safe-encode the digest.

The sha256= prefix is optional in front of any of them. Anything else — a different algorithm prefix, or a value that does not decode to exactly 32 bytes — fails closed with 401.

If you get an unexpected 401 on a signed request, a successfully persisted entry in the delivery log tells you which of three things went wrong: the header could not be parsed (its encoding is not one of the forms above), the digest did not match (the signing secret differs, or the sender signed something other than the exact raw body StackJack received), or the automation has no signing secret provisioned yet — click Rotate secrets once to provision one.

The Webhook card includes copy-paste curl examples — an unsigned one always, and a signed one that renders only after you click Reveal (so the secret never sits hidden on the page).

Rotating the secrets

Click Rotate secrets and confirm. Rotation generates a new URL secret and a new signing secret, and it takes effect immediately — the old URL stops working the moment you confirm, so update every external sender right away.

Delivery limits and response codes

  • Rate limit: 10 webhook invocations per minute per automation. This check runs before signature verification and deduplication, so an otherwise-identical resend can get 429 with no original run ID instead of a duplicate 200.
  • Transport cap: request bodies over 64 KB are rejected.
  • Durable capacity handling: if the delivery is accepted while execution capacity or the automation's one-at-a-time lease is busy, StackJack returns 202 with a new run ID and status: "Queued". Do not retry that response; StackJack owns the queued work.
  • AI service overloaded: if the AI service (Anthropic) is overloaded when the run tries to start, StackJack can hold the run and return 202 with a new run ID, status: "LaunchHeld", and held: true. StackJack tries again automatically. Do not retry that response either.
  • Byte-identical redeliveries: new automations have raw-body deduplication enabled. If a resend reaches deduplication and matches a successfully persisted receipt inside the default 60-second window, it returns 200 with duplicate: true and the original run ID, and creates no second run. Rate limiting can stop the request first, and a failed receipt write leaves nothing to match. Existing automations were not backfilled and may retain no-dedup behavior.
  • Ambiguous 500: no run ID does not prove no run exists. A Pending run may already have been saved and can later recover and execute. Do not retry unconditionally: the endpoint provides no idempotency guarantee for this outcome, and a retry can duplicate work.
  • Never retry a 402. On this endpoint a 402 is a stable business refusal only the organization can clear — it is out of automation credits (error: "insufficient_credits"), or its Agent Runner plan ended and its own Anthropic key is no longer used for runs (error: "agent_runner_base_lapsed"). Neither carries a Retry-After, and both report retryable: false. Contrast the 429 rate limit above and the 503 launch fault, which are retryable.

Use the canonical webhook response table for every status, response shape, and retry rule. See Triggers and Execution Guarantees for what happens to an accepted queued run after the response.

Webhook receipts (delivery log)

StackJack attempts to log application-level outcomes against a known, active automation. Receipt writes are best-effort, and a missing receipt does not prove that no request or run exists. The receipt list normally helps with delivery diagnosis, but a missing receipt does not prove that the sender never reached the automation or that no run exists.

Attempts against unknown, inactive, or archived automations get a 404 and are deliberately not submitted to the log. A request rejected by the web server before the webhook controller runs — for example, at the transport-size boundary — may also have no receipt. If an expected receipt is missing, keep the automation ID, approximate UTC time, sender address, and HTTP response for StackJack support.

  • Inline panel: the automation detail page includes a collapsed Recent webhook receipts panel showing the last 20 receipts with a Refresh link.
  • Full page: choose Webhook logs from a webhook automation's detail-page action row. The dedicated /automations/<automation-id>/webhook-logs page shows up to 500 receipts with a limit selector (50 / 100 / 250 / 500) and a Refresh button.

Each receipt row shows: received time, outcome, the HTTP status returned to the sender, the sender's IP address, payload size in bytes, and a short detail string. Possible outcomes:

OutcomeMeaning
AcceptedA run was accepted for immediate execution or the durable queue. The receipt records the new run's ID.
DuplicateNo new run was created. The response and receipt resolve to the original delivery's run ID.
WrongTriggerTypeThe automation is not webhook-triggered.
InvalidSecretThe URL secret was wrong.
InvalidSignatureThe X-StackJack-Signature header failed verification.
RateLimitedThe 10/minute limit was hit.
PayloadTooLargeThe payload exceeded the size cap.
CapacityExceededThe delivery could not be queued and all execution slots were busy. The 429 response includes Retry-After: 30.
TenantDisabledAutomations are disabled for the tenant.
OrchestrationFailedThe orchestration call threw and the endpoint returned 500. This outcome is ambiguous: a Pending run may already exist even though the response and receipt carry no run ID.
CreditExhaustedThe tenant is out of credits; the run was refused at pre-flight (HTTP 402).
EntitlementLapsedThe organization's own Anthropic key is no longer usable because its Agent Runner plan ended; the run was refused at pre-flight (HTTP 402, error: "agent_runner_base_lapsed"). Not retryable — restore the plan or remove the key. See When an Agent Runner plan ends.
LaunchFailedThe run was created but its background monitor failed to launch (HTTP 503).

Requests aimed at an unknown automation ID are deliberately not submitted to the receipt log (internet scanner noise); the same applies to inactive and archived automations. A tenant-side kill switch (TenantDisabled) is normally submitted, subject to the same best-effort persistence described above.