Signature and retries
How to verify the HotCodePush-Signature header and deduplicate deliveries, when a failed delivery is retried and when a webhook is disabled.
A raw delivery carries HotCodePush-Signature: t=<unix seconds>,v1=<hex>. v1 is the HMAC-SHA256 of "<t>.<body>", the timestamp, a dot and the raw request body, keyed with the webhook's secret. Verify it before you parse the body, and answer 401 to a request that fails.
Three details make the check correct:
- Compute the HMAC over the body exactly as received, not re-serialized.
- Refuse a timestamp more than 5 minutes from your clock, which bounds a replay.
- Compare the two values in constant time.
import { createHmac, timingSafeEqual } from "node:crypto";
import { createServer } from "node:http";
const secret = process.env.HOTCODEPUSH_WEBHOOK_SECRET!;
const toleranceSeconds = 5 * 60;
function isSignatureValid(header: string | undefined, body: string): boolean {
const fields = new Map((header ?? "").split(",").map((field) => field.split("=") as [string, string]));
const timestamp = Number(fields.get("t"));
const signature = fields.get("v1") ?? "";
if (!Number.isInteger(timestamp) || Math.abs(Date.now() / 1000 - timestamp) > toleranceSeconds) {
return false;
}
const expected = createHmac("sha256", secret).update(`${timestamp}.${body}`).digest("hex");
return expected.length === signature.length && timingSafeEqual(Buffer.from(expected), Buffer.from(signature));
}
createServer((request, response) => {
let body = "";
request.on("data", (chunk) => (body += chunk));
request.on("end", () => {
if (!isSignatureValid(request.headers["hotcodepush-signature"] as string | undefined, body)) {
response.writeHead(401).end();
return;
}
const event = JSON.parse(body);
console.log(request.headers["hotcodepush-delivery-id"], event.type, event.id);
response.writeHead(204).end();
});
}).listen(3000);Deduplication and ordering
Every delivery, in every format, carries HotCodePush-Delivery-Id, which stays the same across the retries of one delivery and across a redelivery. The payload's id is the event's id, the same across every webhook that receives the event. Keep the ids you have handled and skip a repeat.
Every delivery also sends User-Agent: HotCodePush-Webhooks/1.0 (+https://hotcodepush.com/docs/webhooks). Allow that value when a firewall sits in front of your endpoint.
Deliveries can arrive out of order: each one is retried on its own, so a release.paused can land before the release.created of the same release. The snapshot carries the object's state at the moment of the event. A receiver that needs the current state reads it from the API reference by objectId.
Retries
Each attempt waits 10 seconds for your answer. Any 2xx status is success, and every other status, a timeout and a refused connection count as a failure. A failed attempt is sent again on this schedule, 6 attempts over about half a day:
| Attempt | Sent after |
|---|---|
| 2 | 1 minute |
| 3 | 5 minutes |
| 4 | 30 minutes |
| 5 | 2 hours |
| 6 | 8 hours |
After the sixth failed attempt the delivery is failed for good and stays in the delivery log for a redelivery. Answer 2xx as soon as you have stored the event, and do the work afterwards.
Auto-disable
Every delivery that fails its last attempt lengthens the webhook's streak, consecutiveFailureCount, and a success resets it to 0. At 50 the webhook is disabled: isEnabled turns off, the audit log records webhook.disabled, and the organization's members get a mail. A disabled webhook receives nothing, and a delivery already pending for it becomes failed without a request.
The Enable action in the webhook's Actions menu, hotcodepush webhook update or an update with isEnabled: true switches it on again and resets the streak. Events from while it was off are not delivered afterwards, but they stay in the audit log.
Last updated on
Events and payload
Every event type a HotCodePush webhook can deliver, grouped by object, and the JSON payload each one carries with its snapshot of the object.
Telemetry export
What the HotCodePush telemetry export sends to an OTLP/HTTP endpoint, the log record and metric names, how to set it up with Grafana Cloud, Honeycomb or a collector, and how deliveries are retried.