# Datacake Webhooks

Datacake, the IoT platform, sends a JSON webhook to your endpoint when a device measurement is
recorded, a payload decoder returns output, or a downlink is sent. Datacake webhooks are not
signed. Instead you add a secret header under **Headers** on the webhook, and your endpoint
authenticates each request by comparing that header with the value you stored.

> Datacake webhooks are not signed. Add a secret header under Headers on the webhook and enter it here.

## When to Use This Skill

* How do I receive Datacake webhooks?
* How do I authenticate a Datacake outgoing webhook? (Datacake has no signature: use a secret header)
* How do I handle `device_measurement`, `decoder_output` or `downlink` events?
* Why does my Datacake webhook endpoint return 401?
* How do I secure a Datacake webhook that points at Node-RED or my own server?

## Verification (core)

```javascript
const crypto = require('crypto');

// Hash both sides: equal-length buffers, so timingSafeEqual never throws.
function safeEqual(received, expected) {
  const ha = crypto.createHash('sha256').update(Buffer.from(received, 'latin1')).digest();
  const hb = crypto.createHash('sha256').update(Buffer.from(expected, 'utf8')).digest();
  return crypto.timingSafeEqual(ha, hb);
}

function verifyDatacakeRequest(
  headers,
  headerName = process.env.DATACAKE_WEBHOOK_HEADER || 'Authorization',
  secret = process.env.DATACAKE_WEBHOOK_SECRET
) {
  if (!secret) return false;                          // never accept when unconfigured
  const received = headers[headerName.toLowerCase()]; // Node lowercases header names
  return typeof received === 'string' && received.length > 0 && safeEqual(received, secret);
}

```

* The header name is whatever you typed under **Headers** in Datacake: `Authorization` or any custom
  name. Look it up case-insensitively (`DATACAKE_WEBHOOK_HEADER`, default `Authorization`).
* Compare the **whole** value (`DATACAKE_WEBHOOK_SECRET`) exactly as typed in Datacake. If you
  entered `Bearer ABC123`, the secret is `Bearer ABC123`. Don't strip or parse a `Bearer ` prefix.
* Python: `hmac.compare_digest` on bytes (encode both sides; passing `str` raises on non-ASCII).
* Return `401` on a missing or mismatched header, `400` for a body that isn't a JSON object, `200`
  once handled.
* Read the body raw (`express.raw({ type: '*/*' })`) and authenticate before parsing it.
* **A static shared secret authenticates the sender, not the body, and gives no replay protection**
  (no signature, no timestamp). Use HTTPS and rotate the secret if it leaks. Don't rely on IP
  allowlisting: Datacake's infrastructure scales dynamically, so its IPs aren't stable.

> **For complete handlers with tests**, see [examples/express/](https://github.com/hookdeck/webhook-skills/tree/main/skills/datacake-webhooks/examples/express/), [examples/nextjs/](https://github.com/hookdeck/webhook-skills/tree/main/skills/datacake-webhooks/examples/nextjs/), [examples/fastapi/](https://github.com/hookdeck/webhook-skills/tree/main/skills/datacake-webhooks/examples/fastapi/).

## Event Types

The body is `{ "event": ..., "data": ... }`. Dispatch on `event`; it is chosen per webhook under
**Events to send**.

| Event | Sent When | `data` |
| --- | --- | --- |
| `device_measurement` | New measurement values are recorded in the database | Array of `{ field, value, device, timestamp }` for the fields in the last message only |
| `decoder_output` | A payload decoder returns output (raw, including values not stored) | Object with `result` (array of `{ field, value, device }`), `log`, `device`, `product`, `execution_time` |
| `downlink` | A downlink is sent to a LoRaWAN or MQTT device (manual or via rules) | Object with `meta` (`type`, `fport`, `downlink`), `device`, `payload` (base64), `product_id`, `product_slug` |

## Environment Variables

```bash
DATACAKE_WEBHOOK_HEADER=Authorization            # the header name you added under Headers
DATACAKE_WEBHOOK_SECRET=Bearer your-long-token   # the exact, whole header value

```

## Local Development

Datacake can't reach `localhost`. Tunnel with the Hookdeck CLI (no account required):

```bash
npx hookdeck-cli listen 3000 datacake --path /webhooks/datacake

```

Use the printed URL as the **Endpoint URL** of the webhook in Datacake.

## Resources

* [references/overview.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/datacake-webhooks/references/overview.md) - What Datacake webhooks are, events and payload shapes
* [references/setup.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/datacake-webhooks/references/setup.md) - Add the webhook and its secret header in Datacake
* [references/verification.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/datacake-webhooks/references/verification.md) - Header check details, gotchas, debugging 401s
* [examples/](https://github.com/hookdeck/webhook-skills/tree/main/skills/datacake-webhooks/examples/) - Runnable Express, Next.js and FastAPI handlers with tests