# Guide to Datacake Webhooks: Features and Best Practices

Datacake is an IoT platform that collects data from LoRaWAN, MQTT, Particle and other devices, decodes it and stores it as measurements. Outgoing webhooks are how that data leaves Datacake: each time a measurement is recorded, a payload decoder runs or a downlink is sent, Datacake can post the details to your own system.

This guide covers how Datacake webhooks are configured and authenticated, what each of the three events contains, what Datacake leaves undocumented, and the practices that keep a Datacake receiver reliable.

## What are Datacake webhooks?

A Datacake webhook belongs to a workspace. It has an endpoint URL, an optional description, a list of events to send and optional custom headers. When one of the chosen events happens anywhere in the workspace, Datacake sends a `POST` over HTTPS with a JSON body that names the event and carries its data.

Datacake does not sign webhooks. Its recommended way to authenticate them is a header you add yourself: a name such as `Authorization` and a secret value known only to Datacake and your receiver. Your endpoint compares the header with the value it has stored and rejects anything else. Datacake advises against IP allowlisting, because its cloud infrastructure scales up and down and the sending addresses are not reliable.

Two common uses are syncing measurements into your own database, and acting on decoder output that Datacake does not store, which avoids paying for that storage.

## Datacake webhook features

| Feature | Details |
| --- | --- |
| Configuration | **Workspace sidebar > Integrations > Webhooks** |
| Scope | Per workspace, with the events you choose under **Events to send** |
| Method and format | `POST` over HTTPS, JSON body |
| Body | `{ "event": ..., "data": ... }` |
| Events | `device_measurement`, `decoder_output`, `downlink` |
| Authentication | A custom header you add under **Headers**, such as `Authorization: Bearer <token>` |
| Signature and timestamp | None |
| Retries and timeout | Not documented |
| Source IPs | Not published; Datacake says IP allowlisting is unreliable |
| Monitoring | Seven-day request chart and error rate per webhook, plus recent requests with your server's responses |

## Events and payloads

Every body has the same two keys: `event`, which names the event, and `data`, whose shape depends on it. Dispatch on `event`.

### Device measurements

`device_measurement` fires only when new measurement values are written to the database. `data` is an array of rows, and it holds only the fields inserted by the device's last message, not every field on the device. A device that sends one field per MQTT message produces one row per webhook.

```json
{
  "data": [
    {
      "field": "TEMPERATURE",
      "value": 19.141228,
      "device": "f7384fd1-9e7f-4540-b4ed-5fa5810bcc27",
      "timestamp": 1647852981
    },
    {
      "field": "HUMIDITY",
      "value": 66.95572,
      "device": "f7384fd1-9e7f-4540-b4ed-5fa5810bcc27",
      "timestamp": 1647852981
    }
  ],
  "event": "device_measurement"
}

```

`timestamp` is in Unix seconds and `device` is the Datacake device ID. In Datacake's longer example the same field appears more than once with different timestamps, so store each row rather than assuming one value per field.

### Decoder output

`decoder_output` carries the raw output of a device's payload decoder, the same data you see in the device debug view. `data.result` is the array the decoder returned, `data.log` holds console output and `data.device` holds device information for LoRaWAN and Particle device types. Both `log` and `device` can be `null`, and a row whose field had no value has no `value` key.

```json
{
  "data": {
    "log": null,
    "device": null,
    "result": [
      {
        "field": "PM25",
        "value": 4.88,
        "device": "dev:864475044240892"
      },
      {
        "field": "RSSI",
        "device": "dev:864475044240892"
      }
    ],
    "product": "ef6275c7-3e9b-4134-9bef-24bc276a9592",
    "execution_time": 664535
  },
  "event": "decoder_output"
}

```

A decoder normally returns a list of field, value and device entries for Datacake to store. Because the webhook forwards whatever the decoder returns, you can add extra information that Datacake ignores but your system uses.

### Downlinks

`downlink` fires whenever a downlink is sent to a LoRaWAN or MQTT device, whether someone sent it by hand or a rule sent it automatically.

```json
{
  "data": {
    "meta": {
      "type": "lorawan",
      "fport": 1,
      "downlink": "a6895656-8198-41fe-9c0e-f49074311190"
    },
    "device": "a52e6324-2cf9-4534-b523-819d94299414",
    "payload": "AQI=",
    "product_id": "4e8e8623-18c4-4798-9877-699058a00e24",
    "product_slug": "temperatur-dach"
  },
  "event": "downlink"
}

```

`payload` is the downlink bytes in base64.

> **Inspect Datacake webhooks as they arrive.** Point a webhook at a [Hookdeck Inspect](https://inspect.hookdeck.com) URL to see each request's headers and body, with no account or setup required.

## Setting up Datacake webhooks

Generate a long random token first, for example with `openssl rand -hex 32`.

1. In the workspace sidebar, open **Integrations > Webhooks** and click **Add Webhook**.
2. Enter your HTTPS **Endpoint URL** and, if you like, a **Description**.
3. Under **Events to send**, choose the events you need.
4. Under **Headers**, add your secret header. Datacake's example uses the name `Authorization` and the value `Bearer ABC123`; use your own token in place of `ABC123`. Any header name works.
5. Click **Create Webhook**.

To route through Hookdeck, create a Hookdeck source with the **Datacake** type and use its URL as the webhook's endpoint URL. Datacake webhooks are not signed, so add a secret header under **Headers** on the webhook and enter it in the source:

* **API Key Header**: the header name you used in Datacake, such as `Authorization`. Hookdeck looks it up case-insensitively.
* **API Key**: the header's whole value, exactly as you typed it in Datacake. If the value is `Bearer <token>`, enter `Bearer <token>`, including `Bearer` and the space.

The source accepts `POST` only, which is the method Datacake uses.

## Securing Datacake webhooks

Read the header you chose, compare its whole value with the one you stored in constant time, and reject the request if either is missing. Do the check before you parse the body.

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

const HEADER_NAME = process.env.DATACAKE_WEBHOOK_HEADER || 'Authorization';
const HEADER_VALUE = process.env.DATACAKE_WEBHOOK_SECRET; // e.g. "Bearer <token>"

function safeEqual(received, expected) {
  const a = Buffer.from(received, 'utf8');
  const b = Buffer.from(expected, 'utf8');
  // timingSafeEqual throws on unequal lengths, so check first.
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}

const app = express();

app.post('/webhooks/datacake', express.raw({ type: '*/*' }), (req, res) => {
  const received = req.get(HEADER_NAME);
  if (!HEADER_VALUE || typeof received !== 'string' || !safeEqual(received, HEADER_VALUE)) {
    return res.sendStatus(401);
  }

  let body;
  try {
    body = JSON.parse(req.body.toString('utf8'));
  } catch {
    return res.sendStatus(400);
  }

  // Store body.event and body.data, respond, then process asynchronously.
  res.sendStatus(200);
});

```

The same check in Python:

```python
import hmac
import os

HEADER_NAME = os.environ.get("DATACAKE_WEBHOOK_HEADER", "Authorization")
HEADER_VALUE = os.environ.get("DATACAKE_WEBHOOK_SECRET", "")  # e.g. "Bearer <token>"

def verify_datacake_request(headers) -> bool:
    if not HEADER_VALUE:
        return False  # never accept when unconfigured
    received = headers.get(HEADER_NAME)  # case-insensitive in Starlette and Flask
    if not received:
        return False
    # Compare bytes: compare_digest raises TypeError on a non-ASCII str.
    return hmac.compare_digest(received.encode("utf-8"), HEADER_VALUE.encode("utf-8"))

```

The header value is static. It authenticates the sender, not the body, and with no timestamp a captured request can be replayed. Datacake sends every request over HTTPS, so the token is encrypted in transit; keep it out of repositories and shared documents, and rotate it if it might have leaked. Datacake also suggests rotating tokens periodically.

> **Make Datacake webhooks production-ready.** [Hookdeck Event Gateway](/event-gateway) checks the header you configure, stores every measurement durably, and retries delivery to your service on a schedule you set.

## Datacake webhook limitations and pain points

### Delivery guarantees are not documented

**The Problem:** You cannot tell from the docs what happens to a measurement when your endpoint is down or slow.

**Why It Happens:** Datacake documents the events and the payloads, but not a timeout, a retry policy or a way to resend a failed request. The webhook list shows the error rate and the recent requests with your server's responses, which tells you something failed but not whether Datacake tried again.

**Workarounds:**

* Respond with a `2xx` as quickly as you can and process the data afterwards.
* Check the error rate in the webhook list after deployments and outages.
* If you need a complete history, compare what you received with the data stored in Datacake after an outage.

**How Hookdeck Can Help:** Hookdeck accepts each request as soon as it arrives and stores it, then delivers to your service with [retries](/docs/retries) you configure. You can replay any request after a fix, and [issues](/docs/issues) tell you when deliveries start failing.

### One webhook carries the whole workspace

**The Problem:** A single endpoint receives measurements from every device, and the volume grows with your fleet.

**Why It Happens:** Webhooks are configured per workspace and per event, not per device or product. Every measurement from every device that records one is sent, and a device that sends a field per message produces a request per field.

**Workarounds:**

* Choose only the events you need under **Events to send**.
* Batch database writes on your side instead of writing each row as it arrives.

**How Hookdeck Can Help:** Hookdeck [filters](/docs/filters) can route on `event`, on a device ID or on a field, and you can [cap the delivery rate](/docs/destinations#set-a-max-delivery-rate) to your service so a burst of uplinks never overwhelms it.

### The payload is not a stable schema

**The Problem:** Code that expects every field to have a value, or one row per field, breaks on real data.

**Why It Happens:** `decoder_output` forwards whatever the decoder returns, including fields with no value, `null` logs and extra keys your decoder adds. `device_measurement` contains only the fields in the latest message and can repeat a field with different timestamps.

**Workarounds:**

* Treat a missing `value` as "no reading", not zero.
* Key stored rows on device, field and timestamp.
* Version your decoder's extra output if other systems depend on it.

**How Hookdeck Can Help:** Hookdeck [transformations](/docs/transformations) can normalise the body, for example by dropping rows with no value, before it reaches your service.

### No signature and no stable IPs

**The Problem:** You have only a static header to tell Datacake's requests apart from anyone else's.

**Why It Happens:** Datacake does not sign the body and does not publish a stable set of sending IPs.

**Workarounds:**

* Use a long random token, compare it in constant time and rotate it periodically.
* Store measurements idempotently, so a replayed request does no harm.

**How Hookdeck Can Help:** Hookdeck's Datacake source compares the header you name with the exact value you enter. Hookdeck answers with a `200` either way, but requests that fail the check are recorded as failed verification and never reach your service.

## Best practices

### Always add a secret header

A webhook without one sends nothing your endpoint can check. Use a long random value, and give each environment its own.

### Compare the whole value

If you typed `Bearer <token>` in Datacake, compare against `Bearer <token>`, not the token alone. Datacake's own troubleshooting notes warn about whitespace and encoding problems in the header value, so copy it exactly.

### Store measurements idempotently

Use device, field and timestamp as a natural key so the same row arriving twice is written once. See our [guide to webhook idempotency](/webhooks/guides/implement-webhook-idempotency).

### Acknowledge fast and process asynchronously

Check the header, store the body, return `200`, and do the database work afterwards. See [why to process webhooks asynchronously](/webhooks/guides/why-implement-asynchronous-processing-webhooks).

### Watch the error rate

Datacake's webhook list shows the error rate and your server's recent responses. Check it after every deployment of your receiver.

## Conclusion

Datacake sends `device_measurement`, `decoder_output` and `downlink` events as JSON `POST` requests over HTTPS, with no signature and no documented retry policy. Add a long random secret header to every webhook, compare its whole value in constant time, respond quickly, and store measurements so that repeats do no harm.

[Hookdeck Event Gateway](https://hookdeck.com) checks the header, keeps every request, and retries and replays delivery to your service, so an outage on your side does not leave gaps in your device data.

[Get started with Hookdeck](https://dashboard.hookdeck.com/signup) for free and handle Datacake webhooks reliably in minutes.