← Docs

Webhooks

Nextfarm sends a POST to your system when something happens on a farm. The farm owner adds a receiving URL (https://) and picks events in Manage › Webhooks.

Events

EventGroupWhen
iot.alert.raisedIoTA sensor crossed a threshold: device, metric, value, threshold, severity.
iot.device.offlineIoTA device stopped reporting for longer than allowed.
harvest.batch.createdHarvestA harvest batch was declared and confirmed: parcel, crop type, yield (kg).

Outgoing message

Example
POST https://erp.your-company.com/hooks/nextfarm
Content-Type: application/json
X-Nextfarm-Event: iot.alert.raised
X-Nextfarm-Delivery: 0199...          (delivery ID — use it to de-duplicate)
X-Nextfarm-Timestamp: 1790467200      (Unix seconds)
X-Nextfarm-Signature: sha256=9c1e...

{
  "id": "0199...", "type": "iot.alert.raised",
  "workspace_id": "...", "occurred_at": "2026-09-27T03:10:00Z",
  "data": { "alert_id": "...", "device_id": "...", "device_code": "NK2", "zone_id": "...",
            "parcel_ids": [], "metric": "temperature", "value": 41.2, "threshold": 38,
            "severity": "high", "message": "..." }
}
EventFields in data
iot.alert.raisedalert_id, device_id, device_code, zone_id, parcel_ids, metric, value, threshold, severity, message
iot.device.offlinedevice_id, device_code, device_name, zone_id, parcel_ids, last_heartbeat
harvest.batch.createdharvest_record_id, parcel_id, crop_type_id, quantity_kg, confirmed_at

Verifying the signature

X-Nextfarm-Signature = sha256= + hex of HMAC-SHA256(secret, timestamp + "." + body). The secret (whsec_…) is shown once, when the URL is created or rotated. Reject messages whose timestamp is off by more than 5 minutes.

Node.js (Express)
import crypto from "node:crypto";
// needs the RAW body — express.raw, not express.json
app.post("/hooks/nextfarm", express.raw({ type: "application/json" }), (req, res) => {
  const ts = req.header("X-Nextfarm-Timestamp");
  const expected = "sha256=" + crypto.createHmac("sha256", process.env.NEXTFARM_WEBHOOK_SECRET)
    .update(ts + "." + req.body).digest("hex");
  const got = req.header("X-Nextfarm-Signature") ?? "";
  const ok = got.length === expected.length && crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(got));
  if (!ok || Math.abs(Date.now() / 1000 - Number(ts)) > 300) return res.sendStatus(401);
  const event = JSON.parse(req.body);   // de-duplicate on X-Nextfarm-Delivery
  res.sendStatus(200);
});
Python (Flask)
import hmac, hashlib, time
@app.post("/hooks/nextfarm")
def hook():
    ts = request.headers["X-Nextfarm-Timestamp"]
    body = request.get_data()
    expected = "sha256=" + hmac.new(SECRET.encode(), f"{ts}.".encode() + body, hashlib.sha256).hexdigest()
    if not hmac.compare_digest(expected, request.headers.get("X-Nextfarm-Signature", "")) \
       or abs(time.time() - int(ts)) > 300:
        return "", 401
    return "", 200

Retries and pausing

The receiving URL must be https:// to a public server. Nextfarm rejects private addresses (localhost, 10.x, 192.168.x, 169.254.x…) both when saving and when sending.

Full security rules for partners: Security →