Skip to main content

Developer resources v1

Coilinx API

Read-only inventory sync and signed operational webhooks.

Verify webhook signatures

Each delivery includes:

X-Coilinx-Event-Id: evt_01abc
X-Coilinx-Signature: t=1784043130,v1=4d9d...

The signed message is <timestamp>.<raw UTF-8 request body>. Compute HMAC-SHA256 with the endpoint's whsec_... secret and compare signatures using a constant-time function.

Reject timestamps more than five minutes from your server clock to limit replay attacks. Store processed event IDs to prevent duplicate business actions.

Node.js

import crypto from "node:crypto";

export function verifyCoilinxWebhook(rawBody, signatureHeader, secret) {
  const entries = Object.fromEntries(
    signatureHeader.split(",").map((part) => part.split("=", 2)),
  );
  const timestamp = Number(entries.t);
  const signature = entries.v1;

  if (!Number.isFinite(timestamp) || !signature) return false;
  if (Math.abs(Date.now() / 1000 - timestamp) > 300) return false;

  const expected = crypto
    .createHmac("sha256", secret)
    .update(`${timestamp}.`)
    .update(rawBody)
    .digest("hex");

  const actualBuffer = Buffer.from(signature, "hex");
  const expectedBuffer = Buffer.from(expected, "hex");
  return (
    actualBuffer.length === expectedBuffer.length &&
    crypto.timingSafeEqual(actualBuffer, expectedBuffer)
  );
}

Pass the raw Buffer, not a parsed-and-stringified JSON object.

Python

import hashlib
import hmac
import time


def verify_coilinx_webhook(raw_body: bytes, signature_header: str, secret: str) -> bool:
    entries = dict(part.split("=", 1) for part in signature_header.split(","))
    timestamp = int(entries.get("t", "0"))
    signature = entries.get("v1", "")

    if not timestamp or not signature:
        return False
    if abs(time.time() - timestamp) > 300:
        return False

    signed = str(timestamp).encode() + b"." + raw_body
    expected = hmac.new(secret.encode(), signed, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, signature)

Secret rotation

Webhook signing secrets are shown once when an endpoint is created. To rotate, create a replacement endpoint, deploy its secret, test it from the dashboard, then delete the old endpoint.