Webhook ingestion contract (reference)
Webhook-triggered automations expose a unique, public HTTPS endpoint that external systems (your PSA, RMM, alerting stack, Zapier, and so on) POST to. This page is the full contract for anyone…
Written By Christopher Scaminaci
Last updated 3 days ago
Webhook-triggered automations expose a unique, public HTTPS endpoint that external systems (your PSA, RMM, alerting stack, Zapier, and so on) POST to. This page is the full contract for anyone configuring a sender: the URL model, authentication, optional request signing, payload handling, limits, every response code you can receive, and the best-effort receipt log used to investigate delivery outcomes.
The endpoint
Each webhook automation gets a unique URL of the form:
POST https://<automations-host>/webhooks/{automationId}/{webhookSecret}
Copy the complete URL from the automation's webhook configuration panel in the portal — don't construct it by hand. The secret embedded in the URL path is the primary authentication: anyone with the full URL can trigger the automation (subject to the limits below), so treat it like a password.
Authentication: two independent secrets
Both secrets are 32 random bytes, hex-encoded (64 hex characters). They are generated when the automation is provisioned as (or switched to) a webhook trigger. Switching the automation away from the webhook trigger preserves both secrets, so the URL still works if you switch back.
Optional request signing (HMAC-SHA256)
To protect against body tampering, your sender can sign each request:
- Compute
HMAC-SHA256(rawRequestBody, signingSecret)— the raw 32-byte digest. - Send it in the
X-StackJack-Signatureheader.
StackJack decodes the header back to those raw digest bytes before comparing, so encoding does not affect the verdict. Any of these spellings of the same digest is accepted:
Hex casing is irrelevant, and the sha256= prefix may be combined with any of the encodings. Whatever the spelling, the value must decode to exactly 32 bytes — anything else, including a different algorithm prefix such as sha384=, is rejected.
Signature semantics:
- No header sent → verification is skipped (signing is opt-in per request).
- Header sent, signature wrong → 401 with support code
SJ-WH-SIG-INVALID. When its receipt persists, the log distinguishes an unparseable header (not one of the encodings above, or not 32 bytes once decoded) from a digest mismatch (the header decoded cleanly, but the digest differs — meaning the signing secret or the signed byte range differs from ours; StackJack signs the raw request body). It records the header's length and the reason, never digest values. - Header sent, but the automation has no signing secret yet (it predates signing support) → 401 with support code
SJ-WH-SIG-MISSING-SECRET. Rotate the webhook secrets once to provision a signing secret. StackJack deliberately rejects rather than silently accepting an unverifiable "signed" request.
Rotating secrets
The Rotate action regenerates both secrets at once. The old URL and old signing secret stop working immediately — update every sender when you rotate.
Payload handling
Choose how much of the request body reaches the agent via the automation's payload mode:
Size limits
- Transport cap: 64 KB raw body, absolute. Larger requests are rejected.
- Per-automation payload cap: after payload-mode filtering, the payload must fit the automation's configured limit — default 32 KB, configurable up to the 64 KB transport ceiling. Exceeding it → 413.
Rate limit
Each automation accepts at most 10 webhook invocations per one-minute window. Beyond that, the rate-limited attempt receives 429 and starts no new run. The rate-limit check happens before body reading, signature verification, and deduplication, so a 429 has no original run ID even when the same body may have been accepted earlier. This is separate from execution capacity: a delivery that passes the rate limit but finds all slots busy is normally accepted into the durable run queue.
Byte-identical redelivery handling
Webhook deduplication runs after URL-secret authentication and the per-automation rate-limit check, and after signature verification, but before payload filtering or run creation. For an automation with deduplication enabled, StackJack fingerprints the raw body with its character length and SHA-256 over its UTF-8 bytes.
- If the request reaches the deduplication check and matches a successfully persisted
Acceptedor earlierDuplicatereceipt for the same automation inside the default 60-second window, it starts no second run. - That matched request receives
200withduplicate: trueand the original run ID. Treat this response as accounted for and do not retry it. - This
200is not guaranteed for every otherwise-identical resend. The rate limiter runs first and can return429with no run ID before deduplication. Receipt persistence is also best-effort; if the earlier receipt was not saved, there is no stored match to suppress another run. - A prior failed or rejected delivery never suppresses a retry. Only a delivery that produced a run (or deduplicated to one) can match.
- Whitespace, property order, formatting, or any other body-byte change produces a different fingerprint. Deduplication is not semantic JSON matching.
New automations are created with deduplication enabled. Automations that existed before the setting was introduced were deliberately not backfilled and retain their earlier no-dedup behavior.
Smart filters
A webhook trigger can carry a smart filter: a short typed judgement that decides whether a delivery is worth a run. It is optional and off unless the author adds one.
Where it sits in the pipeline. After URL-secret authentication, the rate limit, signature verification, deduplication and payload-mode filtering, and after the per-automation payload cap — and before any run row is created. Both ends of that placement are deliberate: the filter judges the same filtered payload the run would have received, and a filtered delivery costs no run and no capacity. The filter's own judgement is a fast decision and is priced separately — see Fast Decisions and Your Own TypeSafe Key.
What it judges. The automation's own filtered trigger payload — never the raw body, which would defeat None mode. An automation in None mode therefore cannot carry a filter at all; the save is refused. A delivery that produces no payload is not judged and the run starts.
How the verdict is reached. The author writes one or more questions and at least one runWhen
condition. When all conditions pass, the run starts. StackJack flags payloads that try to
steer the filter on the delivery's receipt.
Failure behaviour is the author's choice. onUnavailable is Run (the default) or Skip.
The whole evaluation, vendor retry included, is bounded at two seconds.
Deduplication interaction. The filter runs after deduplication, so a byte-identical resend
of a delivery that started a run, inside the 60-second window, is answered from the duplicate path
and never re-judged — but a resend that arrives outside the window, or against an automation with
deduplication disabled, is judged again and can reach a different verdict. Filtered is deliberately not in the set of outcomes
that suppress a later duplicate: that set protects recoverable failures, and a filtered delivery was
a decision, not a failure.
Every evaluation is recorded against the automation and shown on its webhook receipts panel with what was asked and what came back. If an automation's stored filter cannot be read, the Portal marks it Not Ready.
Response codes (sender's reference)
This is the canonical response table for the webhook endpoint. Configure sender retries from the final column, not from a blanket “retry every non-2xx” rule.
Invalid JSON is not automatically a 400. In Selected fields mode, invalid JSON or no matching paths produces no trigger payload; the otherwise-valid delivery can still be accepted. The optional example payload is documentation only and is never a validation schema.
Treat a 500 as an ambiguous, at-least-once delivery outcome, not a clean refusal. StackJack can persist a Pending run before a later startup operation throws; the webhook response then has no run ID, while crash recovery can still execute that persisted run. Check run history and any available receipts, and contact support with the automation ID, response time, and sender details when the outcome cannot be reconciled. A missing receipt does not resolve the ambiguity.
See Triggers and Execution Guarantees for queue expiry, cancellation, overlap, and restart behavior after a 202.
The webhook receipt log
StackJack attempts to record each application-level outcome that reaches a known, live automation in its webhook receipt log, visible in the portal (StackJack staff can see a mirror for support). Receipt persistence is best-effort, and a missing receipt does not prove that no request or run exists. A missing receipt therefore does not prove that the request never reached StackJack or that no run was created.
Deliveries that 404 because the automation ID is unknown, inactive, or archived are deliberately not submitted to the receipt log, so internet scanner noise cannot flood it. A 404 caused by the tenant safety switch is normally submitted as TenantDisabled. A request rejected by the web server before the controller runs — for example at the transport-size boundary — may also have no receipt. Treat the log as useful operational evidence, not as an idempotency or delivery ledger; give support the automation ID, approximate UTC time, sender address, and HTTP response when an expected receipt is absent.
Each successfully persisted receipt records:
Retention: receipts are currently kept indefinitely — there is no automatic purge. The portal's log view shows the most recent receipts (up to 500 per query).
Troubleshooting checklist
- Getting 404? Confirm the automation is active and not archived, and that you copied the full current URL (a rotation changes it). A 404 can also mean automations are disabled for your tenant — contact StackJack support.
- Getting 401 with no signature header? Your URL secret is stale — re-copy the URL.
- Getting
SJ-WH-SIG-MISSING-SECRET? Rotate the secrets once to provision a signing secret, then update the sender with the new values. - Getting
SJ-WH-SIG-INVALID? If a receipt exists, check its detail line first — it says whether the header could not be parsed at all or whether it parsed and the digest simply differed. A digest mismatch means the secret or the signed bytes differ: sign the raw body exactly as sent, not a re-serialized or pretty-printed copy of it (a reformatted body is a different byte sequence and can never match). - Getting 429s? Check whether you're over 10/minute for this automation (a successfully saved receipt shows
RateLimited). Because this check runs before deduplication, a rate-limited identical resend has no original run ID; do not assume an earlier delivery is absent. ACapacityExceededreceipt is the capacity fallback; honor itsRetry-After. Normal capacity pressure returns a queued202, not 429. - Getting 500? Do not retry blindly. A
Pendingrun may already exist and can later execute even though the response has no run ID. Check run history and receipts, then contact support with the automation ID and response time if the outcome remains unclear. Retrying accepts duplicate-work risk. - Agent didn't receive expected data? Check the payload mode and, for selected fields, that your dotted paths match the actual JSON structure — non-matching paths are silently omitted.
- Got
200withduplicate: true? Do not send again. Use the returned original run ID to inspect the execution that already accounts for this delivery.
Related pages
More in How automations behave
Run Lifecycle and StatusesStill need help? Ask the team