# 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)

```javascript
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`, default `Authorization`).
  Sigfox forbids standard headers except `Authorization`; any custom name works.
* `SIGFOX_WEBHOOK_SECRET` is the **exact, whole header value**, including any `Bearer ` prefix you
  typed. Compare the whole value; don't strip or parse a prefix.
* Return `401` for a missing or mismatched header. Compare in constant time (check lengths, then
  `crypto.timingSafeEqual`; in Python `hmac.compare_digest` on bytes).
* The request method is also yours to choose: accept `GET`, `POST` and `PUT`, and handle an empty
  body (a `GET` carries 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`, `seqNumber` and `time` (`seqNumber` restarts after 4096 messages).

> **For complete handlers with tests**, see [examples/express/](https://github.com/hookdeck/webhook-skills/tree/main/skills/sigfox-webhooks/examples/express/), [examples/nextjs/](https://github.com/hookdeck/webhook-skills/tree/main/skills/sigfox-webhooks/examples/nextjs/), [examples/fastapi/](https://github.com/hookdeck/webhook-skills/tree/main/skills/sigfox-webhooks/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

```bash
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):

```bash
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](https://github.com/hookdeck/webhook-skills/blob/main/skills/sigfox-webhooks/references/overview.md) - Callback types, methods, variables and payload shapes
* [references/setup.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/sigfox-webhooks/references/setup.md) - Create the custom callback and add the secret header
* [references/verification.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/sigfox-webhooks/references/verification.md) - Header check details, gotchas, debugging 401s
* [examples/](https://github.com/hookdeck/webhook-skills/tree/main/skills/sigfox-webhooks/examples/) - Runnable Express, Next.js and FastAPI handlers with tests