Handling deliveries
When an event occurs that your app is subscribed to, Stok.ly will send a HTTP request to the URL you provided. This page describes how to handle that request.
Request Headers
Each delivery contains the following HTTP headers:
| Header | Example | Description |
|---|---|---|
| Content-Type | application/json | The content type of the request body. This is always "application/json" |
| X-Stokly-Subscription-Id | 8e2e56ea-6ccd-489c-90a7-79249913a9f6 | The subscription ID responsible for this delivery |
| X-Stokly-Delivery-Id | 10b4df71-272a-48ad-8eaa-97ae4136d544 | A unique identifier (UUID) for this delivery. This will be the same across retries of the same delivery |
| X-Stokly-Timestamp | 2026-07-10T10:29:01.619Z | The time this delivery was attempted. This is used to create the signature |
| X-Stokly-Topic | item.created | The topic that triggered the delivery |
| X-Stokly-Payload-Version | 1.0.0 | The payload version. This can be changed per subscription |
| X-Stokly-Signature | sha256= | The signature of the request. This is used to validate the request body |
Verifying the request
You should verify each webhook delivery to ensure it came from Stok.ly. This can be done by generating a signature from the subscription secret, the timestamp header & the request body.
Here is an example written in NodeJS using the node:crypto library:
const signatureHeader = request.headers["x-stokly-signature"];
const timestampHeader = request.headers["x-stokly-timestamp"];
if (!signatureHeader || !timestampHeader) {
sendJson(response, 400, {
error: "Missing required headers (x-stokly-signature, x-stokly-timestamp)"
});
return;
}
const secret = "_aWG2kSLAZjcxLaL13tIYfHh4JrwuZakJquV56xBcXg"
const rawBody = await readRequestBody(request);
const expected = crypto
.createHmac("sha256", secret)
.update(`${timestampHeader}.${rawBody}`)
.digest("hex");
const received = signatureHeader.replace("sha256=", "");
const valid = crypto.timingSafeEqual(
Buffer.from(expected, "hex"),
Buffer.from(received, "hex")
);
if (!valid) {
sendJson(response, 400, { error: "Invalid signature" });
return;
}
// signature verified - start your own logic
// return a 200
sendJson(response, 202, {});Sending a response
Stok.ly considers any 2xx response status as successful. Any other status codes will be treated as an error and will be retried according to our retry logic that is detailed below.
Your server must respond within 5 seconds, otherwise the request will time out.
Retries
Failed requests will be retried a maximum of 15 times with increasing delays
Stok.ly will make 16 attempts in total, consisting of the initial attempt and then up to 15 retries
After each failed delivery attempt, the next request is delayed as follows
| Retry Number | Delay |
|---|---|
| 1 | 30 seconds |
| 2 | 1 minute |
| 3 | 5 minutes |
| 4 | 15 minutes |
| 5 | 30 minutes |
| 6 | 1 hour |
| 7 | 2 hours |
| 8 | 3 hours |
| 9 | 4 hours |
| 10 | 5 hours |
| 11 | 8 hours |
| 12 | 12 hours |
| 13 | 16 hours |
| 14 | 24 hours |
| 15 | 48 hours |
If 3 consecutive deliveries reach the maximum number of retries, the webhook subscription will be automatically disabled.
Updated 2 months ago
