Preview. This site is still being built. Features, prices and terms may change, and nothing here is a binding offer. Read the preview notice

Preview. Still being built; nothing here is a binding offer. Details

Webhooks

Kodkila sends a signed POST to your HTTPS endpoint when something changes on your channel.

Add an endpoint

In the console, open your channel, then Developers › Webhooks. Give an https:// URL on a public host and choose the events. The signing secret, starting whsec_, is shown once. A channel can have up to 20 endpoints. You can send a test ping, read the delivery log and redeliver any event.

Events

TypeSent when
order.createdAn order is placed (seller view: totals, lines, shipping, status)
order.status_changedAn order moves to another status, by the buyer or the seller
product.created, product.updatedA product is added or changed
product.deletedA product is removed (a product with orders is archived instead)
cms.surface.published, cms.surface.unpublishedA home surface goes live, changes or is taken off
webhook.pingYou send a test ping

Delivery

What your endpoint receives
POST /hooks/kodkila HTTP/1.1
Content-Type: application/json
Kodkila-Signature: t=1759300000,v1=5257a869e7ec…
Kodkila-Event-Id: evt_9f1c…
Kodkila-Event-Type: order.created
Kodkila-Delivery-Id: 6b0e…
Kodkila-Delivery-Attempt: 1

{"id":"evt_9f1c…","type":"order.created","created":"2026-10-02T10:00:00.000Z","channel_id":"…","data":{…}}
  • Answer with any 2xx within 10 seconds. Anything else counts as a failure, and redirects are not followed.
  • Failed deliveries are retried after 1 minute, 5 minutes, 30 minutes, 2 hours and 12 hours.
  • After 15 failures in a row the endpoint is turned off. Fix it, then turn it back on in the console.
  • Delivery is at least once and not strictly in order, so skip events whose id you have already handled.
  • The same headers are also sent with a Westmonk- prefix, the old name. Read Kodkila- first.

Check the signature

v1 is the hex HMAC-SHA256 of t, a dot and the raw request body, keyed with your endpoint’s secret. Compute it over the body exactly as received, before any JSON parsing. Accept the event if any v1 matches in constant time and t is within 300 seconds of now. During a secret rotation the header carries two v1 values.

Node.js (Express)
const crypto = require("crypto");
const express = require("express");

const SECRET = process.env.KODKILA_WEBHOOK_SECRET; // whsec_…
const TOLERANCE_S = 300;

function verify(header, rawBody, secret) {
  let t = null;
  const sigs = [];
  for (const part of String(header || "").split(",")) {
    const [k, v] = part.split("=", 2);
    if (k === "t") t = Number(v);
    if (k === "v1") sigs.push(v);
  }
  if (!Number.isInteger(t) || sigs.length === 0) return false;
  if (Math.abs(Math.floor(Date.now() / 1000) - t) > TOLERANCE_S) return false;
  const expected = crypto.createHmac("sha256", secret).update(`${t}.${rawBody}`).digest();
  return sigs.some((s) => {
    const got = Buffer.from(s, "hex");
    return got.length === expected.length && crypto.timingSafeEqual(got, expected);
  });
}

const app = express();
app.post("/hooks/kodkila", express.raw({ type: "application/json" }), (req, res) => {
  const raw = req.body.toString("utf8");
  if (!verify(req.get("Kodkila-Signature"), raw, SECRET)) return res.sendStatus(400);
  const event = JSON.parse(raw);
  // skip event.id if you have seen it, then handle event.type
  res.sendStatus(200);
});
app.listen(3000);
Python (Flask)
import hashlib, hmac, os, time
from flask import Flask, request, abort

SECRET = os.environ["KODKILA_WEBHOOK_SECRET"].encode()  # whsec_…
TOLERANCE_S = 300

def verify(header: str, raw_body: bytes, secret: bytes) -> bool:
    t, sigs = None, []
    for part in (header or "").split(","):
        k, _, v = part.partition("=")
        if k == "t" and v.isdigit():
            t = int(v)
        elif k == "v1":
            sigs.append(v)
    if t is None or not sigs or abs(int(time.time()) - t) > TOLERANCE_S:
        return False
    expected = hmac.new(secret, f"{t}.".encode() + raw_body, hashlib.sha256).hexdigest()
    return any(hmac.compare_digest(expected, s) for s in sigs)

app = Flask(__name__)

@app.post("/hooks/kodkila")
def kodkila_hook():
    raw = request.get_data()  # raw bytes, before any JSON parsing
    if not verify(request.headers.get("Kodkila-Signature", ""), raw, SECRET):
        abort(400)
    event = request.get_json()
    # skip event["id"] if you have seen it, then handle event["type"]
    return "", 200