Jinn docs Console

Webhooks

Start a run with a webhook and Jinn posts its result there once: run.succeeded or run.failed. The body is the run, as GET /v1/runs/{id} returns it.

{
  "id": "evt_7c41e0a9d2b35f8e61c09a4d",
  "type": "run.succeeded",
  "created": "2026-10-06T09:14:03Z",
  "data": { "id": "run_7c41e0a9d2b35f8e61c09a4d", "state": "succeeded", "output": { "url": "https://…", "files": [ … ] }, … }
}

Jinn tries three times: at once, after a minute and after ten minutes. Answer with a 2xx status within 10 seconds. If every try fails, read the run instead.

Check the signature

Webhooks follow Standard Webhooks, signed with Ed25519 (v1a). Each account has its own key. Find its public key (whpk_…) in the console under Account, or at GET /v1/webhooks/public-key.

Each request has three headers:

HeaderWhat it is
webhook-idThe event's id.
webhook-timestampWhen it was signed, in Unix seconds.
webhook-signaturev1a, and the base64 Ed25519 signature of {webhook-id}.{webhook-timestamp}.{body}.

Refuse a request whose signature does not verify, or whose timestamp is more than five minutes from now.

Go:

event, err := jinn.VerifyWebhook(publicKey, r.Header, body, time.Now())

TypeScript:

const event = await verifyWebhook(publicKey, request.headers, await request.text());

Python, with cryptography:

from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey
import base64

key = Ed25519PublicKey.from_public_bytes(base64.b64decode(public_key.removeprefix("whpk_")))
signed = f"{headers['webhook-id']}.{headers['webhook-timestamp']}.{body}".encode()
signature = base64.b64decode(headers["webhook-signature"].split(",", 1)[1])
key.verify(signature, signed)  # raises if it does not match