Skip to Content
DocumentationEvents & Webhooks

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

EventWhenBody
alarm.createdA new alarm is raisedAlarm record
alarm.resolvedAn alarm is marked resolvedAlarm record
shipment.createdA new shipment is createdShipment record
shipment.completedA shipment is completedShipment 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

Never trust the body before verifying 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

ResponseBehaviour
2xxSuccess, the delivery is closed.
429 or 5xxRetried up to 6 times with exponential backoff and jitter.
Any other 4xxTreated as a permanent error, not retried.
Connection error / timeoutRetried.
You may receive the same event twice

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

  1. Use only an https:// address — the panel rejects http://.
  2. Verify the signature on every request; never process an unverified one.
  3. Apply a timestamp tolerance no wider than 5 minutes.
  4. Keep the signing secret in a secret vault, never in code or logs.
  5. De-duplicate with X-Frigolive-Event-ID.
  6. Publish your receiver under a path that is hard to guess.