ColdLatch

Guides

Connect your own system to ColdLatch with a webhook

Published 8 October 2026. For developers. 5 minute read.

Short answer: give ColdLatch an HTTPS address and a secret. Each time a temperature excursion is confirmed, and each time QA decides what happens to an affected batch, ColdLatch sends that address a signed JSON message describing it. Your code checks the signature and does whatever your process needs: open a ticket, place stock on hold in your ERP, notify a team.

A webhook is how ColdLatch reaches a system it has no built-in connection for: a quality system, a ticketing tool, a data warehouse, or an ERP other than SAP S/4HANA. ColdLatch calls you; you do not have to poll.

What it sends today, and what it does not

Setting it up

  1. In ColdLatch, an administrator opens Settings, then Integrations, and adds one of type Webhook.
  2. Enter the URL ColdLatch should call. Use HTTPS.
  3. Enter a secret: a long random string that only ColdLatch and your system know. ColdLatch stores it encrypted and never shows it again.
  4. Save it, then press Send test on its row. ColdLatch sends a test message (below) and reports whether your address answered with success.

The request

ColdLatch sends an HTTP POST with a JSON body and these headers:

HeaderValue
Content-Typeapplication/json
X-ColdLatch-EventThe event name, for example alert.confirmed or action.approved; integration.test for Send test
X-ColdLatch-Signaturesha256= followed by the signature of the body, in hexadecimal

The alert.confirmed message

{
  "alertId": "8f3c2a1e-6b1d-4c0a-9a55-2f0d6f1b7c21",
  "excursion": {
    "id": "c1d2e3f4-0a1b-4c2d-8e3f-4a5b6c7d8e9f",
    "type": "HIGH",
    "startedAt": "2026-10-05T07:30:00.000Z",
    "confirmedAt": "2026-10-05T07:45:00.000Z",
    "peakC": 9.4
  },
  "subject": {
    "kind": "storage_unit",
    "id": "5a6b7c8d-1e2f-4a3b-9c4d-5e6f7a8b9c0d",
    "name": "Vaccine store B"
  },
  "profile": { "name": "Pharma 2-8", "minC": 2, "maxC": 8 }
}
FieldMeaning
alertIdThe alert in ColdLatch. Use it to recognise a message you have already handled.
excursion.typeHIGH (too warm), LOW (too cold) or OFFLINE (the logger stopped reporting)
excursion.startedAt, confirmedAtTimes in UTC, ISO 8601
excursion.peakCThe most extreme temperature so far, in °C. null for OFFLINE.
subject.kindstorage_unit (then name is present) or shipment (then reference is present)
profileThe allowed range that was broken

Send test sends {"test": true, "message": "ColdLatch test event"} with the event name integration.test.

The decision messages

The four action events share one shape. This is action.approved:

{
  "alertId": "8f3c2a1e-6b1d-4c0a-9a55-2f0d6f1b7c21",
  "action": {
    "id": "0b7e6d5c-4a3f-4e2d-9c1b-0a9f8e7d6c5b",
    "ref": "TC-000042",
    "type": "SAP_BLOCK_BATCH",
    "status": "approved",
    "material": "VAC-MMR-2026",
    "batch": "MMR260411",
    "plant": "1710",
    "storageLocation": "0002",
    "quantity": 1200,
    "unit": "EA"
  },
  "decision": {
    "meaning": "approve",
    "by": "qa@example.com",
    "reason": "Exposed above the labelled range; quarantine pending assessment.",
    "signedAt": "2026-10-05T08:10:00.000Z"
  }
}
FieldMeaning
alertIdThe alert this batch belongs to: the same value as in alert.confirmed
action.id, action.refThe proposed block. ref is the short reference shown in ColdLatch and written into the SAP document header.
action.statusThe state after this event: approved, rejected, succeeded or failed
action.material, batch, plant, storageLocation, quantity, unitThe stock concerned. batch is an empty string for stock that is not batch-managed.
decisionOn action.approved and action.rejected: who signed (by, an email address), the reason they typed, and when. by is null when ColdLatch approved by itself in automatic mode.
sap.materialDocumentOn action.succeeded: the SAP material document, as number and year, for example 4900000012/2026
errorOn action.failed: why SAP did not take the block. QA can retry it in ColdLatch; a second failure sends a second message.

An approval for a shipment that is still on the road is sent at once, but the block is posted to SAP only when the shipment is received, so action.succeeded can follow much later.

alert.resolved carries only the alert and the signed decision:

{
  "alertId": "8f3c2a1e-6b1d-4c0a-9a55-2f0d6f1b7c21",
  "decision": {
    "meaning": "resolve",
    "by": "qa@example.com",
    "reason": "Cold-room door left open during a delivery. Staff retrained.",
    "signedAt": "2026-10-05T11:30:00.000Z"
  }
}

Checking the signature

The signature proves the message came from ColdLatch and was not altered. It is the HMAC-SHA256 of the raw request body, keyed with your secret. Compute it over the bytes you received, before parsing the JSON, and compare it with the header in constant time.

// Node.js
const crypto = require('node:crypto');

function isFromColdLatch(rawBody, signatureHeader, secret) {
  const expected = 'sha256=' + crypto.createHmac('sha256', secret).update(rawBody).digest('hex');
  const a = Buffer.from(expected);
  const b = Buffer.from(signatureHeader || '');
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}

Reject anything that fails the check with 401.

How to answer, and what happens if you do not

Your answerWhat ColdLatch does
Any 2xx within 15 secondsMarks the message delivered
5xx, a timeout, or no connectionTries again after 1 minute, 5 minutes, 15 minutes, 1 hour, 2 hours, 4 hours and 8 hours: eight attempts in all, then gives up
401 or 403Stops at once, and tells your other notification channels (email, Slack) that the webhook is rejecting it
Any other 4xxStops at once

Also: How to block a batch in SAP S/4HANA after a temperature excursion