Spell Tome of Webhooks
— Don't Panic. The webhook will retry.
Register
const wh = await cstar.webhooks.create({
name: 'Stripe ticket sync',
url: 'https://api.your-app.com/cstar-webhooks',
events: ['ticket.created', 'ticket.updated', 'ticket.closed']
});
// SAVE THIS NOW. Subsequent reads will not include it.
const secret = wh.signingSecret;Verify (Node)
import express from 'express';
import { constructEvent } from '@cstar.help/js/webhook/node';
app.post('/webhook',
express.raw({ type: 'application/json' }),
(req, res) => {
try {
const event = constructEvent(
req.body.toString('utf8'),
req.headers['x-signature'],
process.env.CSTAR_WEBHOOK_SECRET
);
res.status(200).json({ received: true });
} catch (err) {
res.status(401).json({ error: err.message });
}
}
);Verify (Edge / Workers)
import { constructEventAsync } from '@cstar.help/js/webhook';
export async function POST(request) {
const body = await request.text();
try {
const event = await constructEventAsync(
body,
request.headers.get('x-signature'),
process.env.CSTAR_WEBHOOK_SECRET
);
return new Response('ok', { status: 200 });
} catch (err) {
return new Response(err.message, { status: 401 });
}
}Headers
X-Signature—t=<unix>,v1=<hex>Stripe-style.X-Event-Type— e.g.ticket.created.X-Event-ID— unique. Use as your idempotency key.X-Webhook-ID— your subscription's ID.X-Timestamp— ISO 8601 send time.X-Delivery-Attempt— starts at 1.x-cstar-request-id— the API call that triggered this.
Local dev
# Forward live events to localhost
cstar listen --forward-to http://localhost:3000/webhook
# Fire a synthetic event into every active receiver
cstar trigger ticket.created
cstar trigger boss.spawned --jsonGotchas
- signingSecret is only on the create response. Lose it and you rotate.
- Use the raw body for verification.
JSON.parsefirst and the HMAC fails. - Replay window defaults to 300s. Pass
{ tolerance: <seconds> }to override. - Legacy
sha256=<hex>still verified; no longer emitted. - 10s response budget. Anything slower and the delivery times out — auto-retries follow.