Guide
Webhooks
Signed, retried deliveries of your account's events to your own endpoint.
Subscribe
curl -s https://YOUR-HOST/api/v1/webhooks -H "Authorization: Bearer $JUTSU_KEY" -H "content-type: application/json" \
-d '{"url":"https://example.com/jutsu","events":["task","approval","battle","order"]}'
The response includes the signing secret (whsec_…) once. GET /webhooks/events lists every event;
"*" subscribes to all. Up to 10 webhooks; needs webhooks:write.
Deliveries
Each delivery is a POST with JSON { id, type, createdAt, data } and the headers Jutsu-Event,
Jutsu-Delivery and Jutsu-Signature: t=<unix>,v1=<hex>.
Verify every request
v1 is HMAC-SHA256 of <t>.<raw body> keyed with the secret. Compare in constant time and reject
anything older than 5 minutes:
import { verifyWebhook } from "@jutsu/sdk";
if (!verifyWebhook(secret, rawBody, req.headers["jutsu-signature"])) return res.status(400).end();
Retries and limits
Answer 2xx within 10 seconds. Anything else is retried with backoff (1 min, 5 min, 30 min, 2 h, 6 h,
12 h, 24 h; 8 attempts). After 25 failures in a row the webhook is switched off; fix it and
PATCH it back on. Delivery is at least once, so dedupe on Jutsu-Delivery.
POST /webhooks/{id}/test sends a signed ping now, GET /webhooks/{id}/deliveries shows the log and
POST /webhooks/deliveries/{id}/retry resends one. POST /webhooks/{id}/rotate issues a new secret.
Safety
URLs must be public HTTPS. Private, loopback, link-local and metadata addresses are refused, both when you save the URL and when we connect (so DNS cannot be rebound to an internal address). Redirects are not followed.