Developer docs · Browse & search
When Jwtson calls you
Hear about runs through webhooks, be called as a tool, and start work from signed events.
These run on your server, and need no client or key. All of them want the raw request body, exactly as it arrived, so read it before any JSON parsing.
Hearing about runs
const subscription = await jwtson.subscriptions.create({ url: 'https://you.example/hooks/jwtson', events: ['run.succeeded', 'run.failed'] });
subscription.secret; // Shown once, here
// In your handler:
const event = await verifyWebhook({ body: rawBody, headers: request.headers, secret: process.env.JWTSON_WEBHOOK_SECRET });
if (await alreadyHandled(event.id)) return respond(200);verifyWebhook throws VerificationError unless the signature matches and is under five minutes old. Answer that with a 401, and don't act on the request.
Deliveries can arrive twice and out of order. Keep the event.ids you've handled, and answer quickly with a 2xx, doing slow work afterwards. Anything else counts as a failure and is retried, and a URL that keeps failing is turned off, with the reason in disabledReason. subscription.deliveries() shows what happened, and subscription.retry() sends one again under the same event id.
To replace a secret, call subscription.rollSecret() and pass both while you move over: secret: [newSecret, oldSecret].
Being called as a tool
const store = myEffectStore(); // Your database. See below
const jwks = remoteJwks('https://<your API>/.well-known/jwks.json'); // Public, and needs no key
const call = await verifyToolCall({
body: rawBody, headers: request.headers,
audience: 'https://you.example/tools/refund', // Exactly as registered
issuer: process.env.JWTSON_ISSUER, jwks,
});
const result = await once(store, call.effectKey, () => issueRefund(call.input));verifyToolCall checks the signature, the issuer, your address, the time, the exact arguments and the effect key. Pin issuer and the keys' address in your configuration, and never read them from the request. The same address gives the issuer to pin. remoteJwks picks up a new signing key by itself.
A tool call can arrive more than once, because a lost answer gets retried under the same effect key. once does the work one time per key and gives every repeat the first answer. It needs a store that remembers: begin should be an insert on a unique key in your database, so two servers can't both win. MemoryEffectStore forgets on restart, so it's only for tests. If work throws, the key is released for a retry, so throw only when nothing changed.
Sending events without a key
A system that can only sign can start work through a trigger, with no API key:
const body = JSON.stringify({ event_id: order.id, payload: order });
await fetch(trigger.eventUrl, { method: 'POST', body, headers: { 'content-type': 'application/json', [SIGNATURE_HEADER]: await signEvent({ body, secret: triggerSecret }) } });The same event_id sent twice counts once.
From the @jwtson/sdk README, as the code stands.