Contents

Docs / Reference

Webhooks

Have Sokie POST to your own systems when a deploy goes live or fails, a server takes its first call, or usage passes your alert.

A webhook tells your own systems when something happens to your servers: a deploy goes live or fails, a server takes its first call, usage passes your alert. Sokie sends a signed POST to an https address of yours each time, and tries again if it's down.

Add one

In Settings, under Workspace, open Webhooks and choose Add a webhook. Give the endpoint's https address and pick its events. Sokie shows the webhook's signing secret once, as whsec_…: save it with the endpoint's other secrets, since it's how the endpoint knows a delivery came from Sokie. Send a test sends a ping to check the endpoint before a real event does.

A workspace can have 5 webhooks. Their addresses must be https, and never one inside a network: Sokie checks what the name resolves to when you add it and before each delivery, and doesn't follow redirects.

Events

  • deploy.live: a production deploy went live. data has server and deploy (id, number, tools, source).
  • deploy.failed: a production deploy failed or timed out. deploy also has error, with a code and a message.
  • server.first_call: a server took its first tool call from a client other than Sokie's own. data has server, at, client and tool.
  • usage.alert: the month's tool calls passed the alert threshold set on Billing. data has month and toolCalls, the threshold that was passed.
  • usage.stopped: tool calls stopped for the month, at the plan's limit or your cap. Same data.
  • upstream.sign_in_broken: a server's shared sign-in to its API was refused, and someone needs to sign in again. data has server.
  • ping: the test Send a test sends.

Pull request previews don't send deploy events; their pull request already shows how they went.

What a delivery looks like

The body is JSON:

{
  "id": "k57a3m9c2x8w1q0e5r4t6y7u8i9o0p1a",
  "type": "deploy.failed",
  "createdAt": "2026-10-22T09:14:03.120Z",
  "workspaceId": "j97b2n8d3y7v2p1f6s5u7z8w9j0k1l2m",
  "data": {
    "server": {
      "id": "js7by1shfy4gy7ne49sqcx4he98gk6gh",
      "name": "Tasks",
      "slug": "patient-tiger-637",
      "url": "https://patient-tiger-637.sokie.cloud/mcp"
    },
    "deploy": {
      "id": "kd7c4p1v9b2n8m3x6z5a4s3d2f1g0h9j",
      "number": 12,
      "error": { "code": "MISSING_SECRETS", "message": "The server uses TASKS_TOKEN, which isn't set." },
      "source": "git"
    }
  }
}

With it come these headers:

  • x-sokie-event: the event, as in type.
  • x-sokie-delivery: the delivery's id, the same on every retry of it, so your endpoint can ignore one it has already handled.
  • x-sokie-signature: t=<seconds>,v1=<hex>, described next.

Check the signature

v1 is an HMAC-SHA256, in hex, of the timestamp, a dot and the body exactly as it arrived, keyed with the webhook's secret. Compute it yourself, compare in constant time, and refuse a timestamp more than five minutes old, so a captured delivery can't be replayed later. In Node or Bun:

import { createHmac, timingSafeEqual } from "node:crypto";

export function fromSokie(body: string, header: string, secret: string): boolean {
  const parts = Object.fromEntries(header.split(",").map((part) => part.split("=")));
  const timestamp = Number(parts.t);
  if (!timestamp || Math.abs(Date.now() / 1000 - timestamp) > 300) return false;
  const expected = createHmac("sha256", secret).update(`${timestamp}.${body}`).digest("hex");
  const given = Buffer.from(parts.v1 ?? "", "hex");
  return given.length === 32 && timingSafeEqual(given, Buffer.from(expected, "hex"));
}

Read the body as text before parsing it: parsing and printing JSON again can change its bytes, and then the signature won't match.

Answers and retries

Answer with any 2xx within 10 seconds, and do the work afterwards. A timeout, a failure to connect, a 5xx, a 408 or a 429 is tried again a minute, 5 minutes and 30 minutes later; any other answer fails the delivery for good. Each webhook's row in Settings shows how its latest delivery went, and Recent deliveries lists the last week's, with the status your endpoint answered. Sokie never keeps what your endpoint answered beyond its status.

Removing a webhook stops its deliveries, including ones waiting for a retry.