Skip to content
Docs
strackt.io Open app

Webhook payloads and signatures

What a strackt webhook delivery looks like — the request, the JSON body, the signature header and how to check it, the events in each category, and how retries work.

Webhook payloads and signatures

This page is the lookup reference for what arrives at your endpoint. To add, pause, or remove an endpoint, see Webhooks.

The request

Every delivery is a single HTTPS request:

Part Value
Method POST
Content-Type application/json
Signature Hex-encoded HMAC-SHA256 of the request body, keyed with your endpoint's signing secret. See Verify the signature.
Body A JSON object with the four fields below

strackt waits 3 seconds for your endpoint to answer. Each endpoint gets its own delivery, signed with its own secret — if two endpoints subscribe to the same category, both receive the event independently.

The body

Every event uses the same envelope:

Field Type Meaning
event string The event name, such as server.offline or operation.completed. Use this to decide what to do.
category string The category the event belongs to — one of the six categories below, or test for a test delivery. This is what your endpoint subscribed to.
timestamp string When strackt prepared the delivery, as ISO 8601 in UTC (for example 2026-09-26T14:03:12+00:00). A retry carries the same value.
data object Details about what happened. The fields depend on the event.

An example server.offline delivery:

{ "event": "server.offline", "category": "server_health", "timestamp": "2026-09-26T14:03:12+00:00", "data": { "server_id": "9d3f6a1e-4b2c-4e8f-9a51-2c7d8e0f1b34", "server_name": "web-01" } }

Values in the examples on this page are illustrative. Read the field names, not the values.

Neither the body nor the headers carry a unique delivery ID. If you need to recognise a repeated delivery, compare the event, the timestamp and the IDs inside data.

Operation events

Deploys, rollbacks, configuration updates, server installs and similar work all run as operations, and they arrive as three events:

Event Sent when
operation.started The operation begins
operation.completed The operation finished successfully
operation.failed The operation stopped with an error

The category depends on what kind of operation it is — a deploy arrives under deployment, a configuration update under configuration, a server install under adoption. So subscribing to Deployments gets you deploy starts, completions and failures, without the configuration traffic.

Automatic updates that strackt applies to your servers send no operation.started or operation.completed event. You get operation.failed if one fails. A summary of the month's updates arrives by email instead — see How strackt keeps your servers updated.

All three carry the same data fields:

Field Type Meaning
operation_id string The operation's ID
operation_type string A stable identifier for the kind of operation, such as ssh-deploy
operation_label string A readable name for the kind of operation, such as Deploy
server_id string or null The server it ran on
server_name string or null That server's name

operation.failed adds two more:

Field Type Meaning
error_message string What went wrong, in plain language
error_code string or null A stable code for the kind of failure, when one is known
{ "event": "operation.failed", "category": "deployment", "timestamp": "2026-09-26T14:03:12+00:00", "data": { "operation_id": "9d3f6b02-7c41-4d0a-b1e3-5f6a7b8c9d01", "operation_type": "ssh-deploy", "operation_label": "Deploy", "server_id": "9d3f6a1e-4b2c-4e8f-9a51-2c7d8e0f1b34", "server_name": "web-01", "error_message": "The build step failed. Check the deploy log, fix the error, and deploy again.", "error_code": null } }

Background housekeeping that doesn't change your site — such as strackt checking which plugins a WordPress site has — is not sent. Server reboots are announced through the maintenance events instead of operation events.

Events by category

When you add an endpoint you choose categories, and you receive every event in the categories you tick. Event names don't always start with their category name — server.offline belongs to server_health — so route on event, and use category only if you need to know why the event reached you.

Besides the operation events above, each category carries:

Category Label in settings Events
deployment Deployments deploy.health_check_failed, deploy.merge_conflict, preview.environment_created, preview.cap_reached, preview.fork_refused
server_health Server Health server.online, server.offline, server.unhealthy, server.http_unreachable, server.alert_fired, server.alert_resolved
configuration Configuration configuration.environment_created, configuration.environment_deleted, configuration.wordpress_update_failed, server.configuration_applied, server.configuration_not_applying
security Security security.ssh_ca_fallback, maintenance.scheduled, maintenance.override_scheduled, maintenance.completed, maintenance.cancelled, maintenance.failed_terminal
adoption Adoption server.adoption_succeeded, server.adoption_failed
platform Platform billing.payment_failed, application.storage_warning, application.storage_critical, server.orphaned_dataset_warning, server.orphaned_dataset_destroyed

Each of these events carries its own data fields — typically the ID and name of the server, application or environment involved, plus details specific to the event. Treat any field you don't recognise as optional, and expect new events and new data fields to appear over time: ignore what you don't handle rather than rejecting the delivery.

Test deliveries

Send test on an endpoint delivers a test.ping event. It's signed and sent exactly like a real event, so it's a good way to check your verification code:

{ "event": "test.ping", "category": "test", "timestamp": "2026-09-26T14:03:12+00:00", "data": { "message": "This is a test webhook from strackt.", "endpoint_id": "9d3f6c55-0e12-4a7b-8c3d-4e5f6a7b8c9d" } }

The test category only ever carries test.ping. You can't subscribe to it; it's sent only when you ask for it.

Verify the signature

The Signature header is the HMAC-SHA256 of the raw request body, keyed with your endpoint's signing secret, written as lowercase hex. To check a delivery:

  1. Read the body exactly as it arrived, before parsing it.
  2. Compute HMAC-SHA256 of those bytes with your signing secret.
  3. Compare the result with the Signature header using a constant-time comparison. If they differ, reject the request.

Always sign the raw bytes. Parsing the JSON and re-encoding it can change the escaping of characters like / and produce a different signature.

Node.js:

import crypto from 'node:crypto'; function isFromStrackt(rawBody, signatureHeader, secret) { const expected = crypto.createHmac('sha256', secret).update(rawBody).digest('hex'); const given = Buffer.from(signatureHeader ?? '', 'utf8'); const wanted = Buffer.from(expected, 'utf8'); return given.length === wanted.length && crypto.timingSafeEqual(given, wanted); }

PHP:

$rawBody = file_get_contents('php://input'); $expected = hash_hmac('sha256', $rawBody, $secret); if (! hash_equals($expected, $_SERVER['HTTP_SIGNATURE'] ?? '')) { http_response_code(401); exit; }

The signature covers the body only. There is no separate timestamp header.

When you rotate the secret, new deliveries are signed with the new secret straight away. A delivery that was already on its way — including its retries — keeps the signature it was given with the old secret, so accept both secrets for a few minutes after rotating.

Responses and retries

Your endpoint What strackt does
Answers with any 2xx status within 3 seconds The delivery counts as successful. The response body is ignored.
Answers with any other status, times out, or can't be reached The attempt counts as failed, and strackt tries again.

A delivery gets up to 3 attempts. After the first failure strackt waits about 10 seconds before trying again, and after the second about 100 seconds. If the third attempt also fails, strackt stops trying for that event — it isn't queued for later, and a later event doesn't resend it. While the most recent delivery is failing, the endpoint shows Needs attention in Settings → Connections → Webhooks; it clears once a later delivery succeeds.

strackt also won't deliver to an address that resolves to a private or internal network. Such a delivery fails straight away, without retrying.

Some practical consequences:

  • Answer fast, then do the work. Return a 2xx as soon as you've verified the signature and stored the event. Anything that takes longer than 3 seconds counts as a failure, even if your code finishes afterwards.
  • Expect the occasional duplicate. If your endpoint handled a delivery but answered too slowly or with a non-2xx status, the same event arrives again. Make your handler safe to run twice.
  • Don't rely on order. Retries and separate deliveries can arrive in a different order from the events that caused them. Use timestamp and the current state in strackt, not arrival order, when order matters.
  • Webhooks are a signal, not a record. A delivery that fails all three attempts is not sent again. If you need a complete history of operations, read it from the REST API.

Was this helpful?