Agent skill
Sigfox Webhooks Skill
Receive and authenticate Sigfox webhooks (device-type custom callbacks). Use when setting up a Sigfox callback endpoint, checking the secret header you add to the callback, accepting GET, POST or PUT requests, debugging 401s, or handling data uplink, bidir, service status and error callbacks.
Install this skill
npx skills add hookdeck/webhook-skills --skill sigfox-webhooks
Sigfox (the IoT network now operated by UnaBiz) pushes device messages to your server through custom callbacks configured on a device type. Callbacks are not signed. You add a secret header to the callback's Headers section, and your endpoint authenticates each request by comparing that header with the value you stored.
A static shared credential authenticates the sender, not the body, and gives no replay protection (no signature, no timestamp). Serve the endpoint over HTTPS and rotate the credential if it leaks.
When to Use This Skill
- How do I receive Sigfox webhooks (callbacks)?
- How do I authenticate a Sigfox custom callback?
- Does Sigfox sign callback requests? (No: use a secret header)
- How do I handle GET, POST and PUT callbacks, including empty bodies?
- How do I handle uplink, bidir, service status or error callbacks?
- Why does my Sigfox callback endpoint return 401?
Verification (core)
const crypto = require('crypto');
function safeEqual(a, b) {
const x = Buffer.from(String(a), 'utf8');
const y = Buffer.from(String(b), 'utf8');
return x.length === y.length && crypto.timingSafeEqual(x, y);
}
function verifySigfoxRequest(headers, secret = process.env.SIGFOX_WEBHOOK_SECRET,
name = process.env.SIGFOX_WEBHOOK_HEADER || 'Authorization') {
if (!secret) return false; // never accept when unconfigured
const received = headers[name.toLowerCase()]; // Node lowercases header names
return typeof received === 'string' && received !== '' && safeEqual(received, secret);
}
- The header name is yours to choose (
SIGFOX_WEBHOOK_HEADER, defaultAuthorization). Sigfox forbids standard headers exceptAuthorization; any custom name works. SIGFOX_WEBHOOK_SECRETis the exact, whole header value, including anyBearerprefix you typed. Compare the whole value; don't strip or parse a prefix.- Return
401for a missing or mismatched header. Compare in constant time (check lengths, thencrypto.timingSafeEqual; in Pythonhmac.compare_digeston bytes). - The request method is also yours to choose: accept
GET,POSTandPUT, and handle an empty body (aGETcarries its variables in the query string). - Read the body raw (
express.raw({ type: '*/*' })) and parse only after the credential check. - Answer within 10 seconds. After a timeout or error code, Sigfox retries 1, 2 and 4 minutes later, so deduplicate on
device,seqNumberandtime(seqNumberrestarts after 4096 messages).
For complete handlers with tests, see examples/express/, examples/nextjs/, examples/fastapi/.
Callback Types
The request does not say which callback type sent it; the variables in your URL or body template decide the shape. The examples infer the type from the fields present. Event callbacks are created under Event configuration, and carry eventType only if your template includes {eventType}.
| Callback type | Sent When | Telltale variables |
|---|---|---|
DATA UPLINK | A message is received from a device | device, deviceTypeId, data, seqNumber, time |
DATA BIDIR | An uplink requests a downlink | ack, longPolling plus the uplink variables |
DATA ADVANCED | Message details arrive (~30 s delay) | lqi, linkQuality, duplicates |
SERVICE STATUS | Device battery/temperature is reported (deprecated) | batt, temp, snr |
SERVICE ACKNOWLEDGE | The network confirms a downlink | infoCode, downlinkAck |
SERVICE REPEATER | A repeater reports status | repeatedMsg, whitelistDevices |
ERROR | A device breaches its keep-alive delay | severity, info |
EVENT | A device lifecycle or subscription event occurs | eventType (for example OFF_CONTRACT), sourceId, severity |
Environment Variables
SIGFOX_WEBHOOK_HEADER=Authorization # header you added to the callback
SIGFOX_WEBHOOK_SECRET=Bearer your-long-random-value # exact whole header value
Local Development
Sigfox can't reach localhost. Tunnel with the Hookdeck CLI (no account required):
npx hookdeck-cli listen 3000 sigfox --path /webhooks/sigfox
Use the printed URL as the callback URL in the Sigfox backend.
Resources
- references/overview.md - Callback types, methods, variables and payload shapes
- references/setup.md - Create the custom callback and add the secret header
- references/verification.md - Header check details, gotchas, debugging 401s
- examples/ - Runnable Express, Next.js and FastAPI handlers with tests