Events and Webhooks
You do not need to poll the API to learn about alarm and shipment events. You
register an HTTPS address, and when an event happens Frigolive sends a signed
POST request to it.
Webhooks are designed to deliver events to a customer’s secure backend service — not for a mobile app to listen to directly.
Setup
Endpoints are managed from Settings → Webhooks in the Frigolive panel. When you save an address a signing secret is generated and shown exactly once. Store it in your receiver’s secret vault.
From the same screen you can send a test event and review the last 100 delivery attempts with their response codes and error messages.
Published events
| Event | When | Body |
|---|---|---|
alarm.created | A new alarm is raised | Alarm record |
alarm.resolved | An alarm is marked resolved | Alarm record |
shipment.created | A new shipment is created | Shipment record |
shipment.completed | A shipment is completed | Shipment record |
Each endpoint receives only the events it subscribed to. Adding new event types
is backwards compatible; if you receive a type you do not know, ignore it
silently.
Request format
POST /your-receiver-address HTTP/1.1
Content-Type: application/json; charset=utf-8
User-Agent: Frigolive-Webhooks/1.0
X-Frigolive-Event: alarm.created
X-Frigolive-Event-ID: 7126f58a-77c7-4d50-aa87-d1dd99297713
X-Frigolive-Delivery-ID: 0fbc2c15-10ab-4b75-a59e-043b23614b60
X-Frigolive-Timestamp: 1785312000
X-Frigolive-Signature: v1=9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08
{
"id": "7126f58a-77c7-4d50-aa87-d1dd99297713",
"type": "alarm.created",
"created_at": "2026-07-27T11:42:00Z",
"data": {
"id": "0fbc2c15-10ab-4b75-a59e-043b23614b60",
"device_id": "a6532895-026f-4dad-9f89-432a33e93bc0",
"shipment_id": 481,
"type": "TEMPERATURE",
"severity": "WARNING",
"source": "THRESHOLD",
"message": "Temperature above configured maximum.",
"resolved": false,
"created_at": "2026-07-27T11:42:00Z",
"resolved_at": null
}
}Verify the signature
Your address is on the public internet; anyone can send it a request. The signature check is the only thing proving the request really came from Frigolive and was not modified in transit.
The signed string is "{timestamp}.{raw body}", signed with HMAC-SHA256. Use the
raw body — if you parse the JSON and re-serialize it the bytes change and the
signature will not match.
import hashlib
import hmac
import time
TOLERANCE_SECONDS = 300 # 5 minutes
def verify(raw_body: bytes, timestamp: str, signature: str, secret: str) -> bool:
# 1) Time window: stops a captured old request from being replayed.
if abs(time.time() - int(timestamp)) > TOLERANCE_SECONDS:
return False
# 2) Recompute the signature.
signed = f"{timestamp}.".encode() + raw_body
expected = "v1=" + hmac.new(
secret.encode(), signed, hashlib.sha256
).hexdigest()
# 3) Compare in constant time so bytes are not leaked one by one.
return hmac.compare_digest(expected, signature)The Node.js equivalent:
import { createHmac, timingSafeEqual } from "node:crypto";
export function verify(rawBody: Buffer, timestamp: string, signature: string, secret: string) {
if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) return false;
const expected =
"v1=" +
createHmac("sha256", secret)
.update(Buffer.concat([Buffer.from(`${timestamp}.`), rawBody]))
.digest("hex");
const a = Buffer.from(expected);
const b = Buffer.from(signature);
return a.length === b.length && timingSafeEqual(a, b);
}What you must respond
Return 2xx within 10 seconds. The body is not read; an empty response is fine.
Do not do heavy work synchronously: write the event to your own queue and return
200 immediately. A slow response times out and causes needless redelivery.
Retries and idempotency
| Response | Behaviour |
|---|---|
2xx | Success, the delivery is closed. |
429 or 5xx | Retried up to 6 times with exponential backoff and jitter. |
Any other 4xx | Treated as a permanent error, not retried. |
| Connection error / timeout | Retried. |
If your response is lost on the network Frigolive retries the delivery. X-Frigolive-Event-ID does not change between retries — store it and skip events you already processed. X-Frigolive-Delivery-ID is different on every attempt and exists for diagnostics.
After 20 consecutive failed deliveries the endpoint is disabled automatically and a warning appears in the panel. Once you have fixed the address you re-enable it from the same screen.
Rotating the secret
You can generate a new signing secret from the panel. The new secret takes effect immediately; if no pending delivery was signed with the old one there is no interruption. To eliminate any gap entirely, write your receiver to accept both secrets during the transition.
Security checklist
- Use only an
https://address — the panel rejectshttp://. - Verify the signature on every request; never process an unverified one.
- Apply a timestamp tolerance no wider than 5 minutes.
- Keep the signing secret in a secret vault, never in code or logs.
- De-duplicate with
X-Frigolive-Event-ID. - Publish your receiver under a path that is hard to guess.