Webhooks
Have TicketStacker call your system the moment a ticket reaches a step, with a signed JSON payload you can trust.
A webhook lets TicketStacker tell another system that something happened: a repair was approved, an order is ready, a ticket closed. You decide exactly when, because webhooks are attached to steps.
Add one
- Open the stack's editor and go to Automations.
- Pick the step, like Ready for pickup or Done.
- Under Changes the ticket, add Call a web address (webhook) and enter your address. It must start with
https://. - Click Publish changes.
From then on, every ticket that arrives in that step sends one call. The ticket's History shows each call. See Automatic actions.
What we send
A POST with a JSON body:
{
"event": "ticket.step_entered",
"sent_at": "2026-10-14T15:20:03Z",
"ticket": {
"id": "8c1e…",
"key": "FR-42",
"title": "Leaking faucet",
"status": { "key": "done", "name": "Done" },
"step": { "key": "fixed", "name": "Fixed" },
"is_closed": true,
"fields": { "location": "South Lamar", "cost": 41250 },
"url": "https://app.ticketstacker.com/stacks/…/t/42"
}
}
ticket is the same object the API returns; see the ticket object. It shows the ticket after the step's other automatic actions ran, so a new owner or a filled-in field is already there.
Headers:
| Header | |
|---|---|
X-TicketStacker-Event |
ticket.step_entered |
X-TicketStacker-Delivery |
A unique ID for this call. Use it to ignore repeats |
X-TicketStacker-Signature |
sha256= and an HMAC of the body (below) |
Check it came from us
Anyone can find your address, so check the signature before you act on a call. Your company's Webhook signing secret is in Settings → API & webhooks. Compute an HMAC-SHA256 of the raw request body with that secret and compare it to the header.
Node.js:
import { createHmac, timingSafeEqual } from "node:crypto";
function fromTicketStacker(rawBody, signatureHeader, secret) {
const expected = "sha256=" + createHmac("sha256", secret).update(rawBody).digest("hex");
return signatureHeader?.length === expected.length &&
timingSafeEqual(Buffer.from(signatureHeader), Buffer.from(expected));
}
Python:
import hmac, hashlib
def from_ticketstacker(raw_body: bytes, signature_header: str, secret: str) -> bool:
expected = "sha256=" + hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest()
return hmac.compare_digest(signature_header or "", expected)
Use the body exactly as it arrived. Parsing the JSON and turning it back into text changes it, and the signature won't match.
Answer quickly, and retries
Reply with any 2xx status within 10 seconds. If you need to do slow work, accept the call first and do the work afterwards.
If your address is down or answers with an error, we try again: after 1 minute, then 4, 9 and 16 minutes, up to 5 tries in all. Thanks to retries the same delivery can arrive twice, so use X-TicketStacker-Delivery to skip one you've already handled.
Good to know
- Webhooks start working once your company is activated.
- Addresses must be public
https://addresses; we don't calllocalhostor private networks. - Need to send data into TicketStacker instead? Use the API.
We run our own platform this way, too: when a new company signs up, our API files it as a ticket in our own TicketStacker, and approving that ticket calls a webhook that switches the company on.
Related
- API referenceCreate, update, move and close tickets from your own code or other tools, with a simple JSON API and a token.
- Connect TicketStacker to other appsUse Zapier, Make or n8n to link TicketStacker with Slack, Google Sheets, QuickBooks and thousands of other apps, without writing code.
- Automatic actionsLet a step assign the owner, fill in a field or call another system, so routine busywork happens by itself.
- Statuses and stepsStatuses are the big picture (New, In progress, Done); steps are the real stages inside them. Here's how to set both up.
Still stuck? Email hello@ticketstacker.com. A real person reads every message.
