Webhooks
Registering webhooks
Panel vs API; what makes a good endpoint URL.
Two ways to register
- Panel. Settings → Webhooks → New. Easiest for a single integration; the secret is shown once on screen and you copy it into your endpoint's config.
- API.
POST /v1/admin/webhooks. Reproducible across environments; pair with Terraform or a setup script.
Both end up writing the same row.
What you need
- A public HTTPS URL. Localhost works only via a tunnel (ngrok, cloudflared) — see Testing locally.
- The list of events you want. See Events for the catalogue.
- A place to store the returned
secret(env var, secret manager).
URL hygiene
- Use a dedicated path.
/sellvik-webhook, not/. Makes traffic obvious in your access logs. - Don't put the secret in the URL. It's already in the payload signature; putting it in the path leaks it to every reverse proxy in the chain.
- Respond fast (< 10s). If your handling is slow, ack first, work later: enqueue the event to a queue and return 200 immediately.
- Idempotent handler. Process the same
event.idtwice without breaking state. See Events → Idempotency. - Stable URL. Webhook deliveries follow redirects up to two hops; beyond that they fail. Don't put a redirector in front of the webhook endpoint.
One-shot registration script
// scripts/setup-webhooks.ts
import "dotenv/config"
const SELLVIK_ADMIN_KEY = process.env.SELLVIK_ADMIN_KEY!
const ENDPOINT = process.env.WEBHOOK_URL! // e.g. https://erp.example.com/sellvik-webhook
const res = await fetch("https://api.sellvik.app/api/v1/admin/webhooks", {
method: "POST",
headers: {
Authorization: `Bearer ${SELLVIK_ADMIN_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
name: "ERP sync",
url: ENDPOINT,
events: [
"order.created",
"order.fulfilled",
"order.cancelled",
"inventory.low_stock",
],
}),
})
if (!res.ok) {
console.error(await res.text())
process.exit(1)
}
const { id, secret } = await res.json()
console.log(`Created webhook ${id}`)
console.log(`Secret (store now, never shown again):\n${secret}`)One subscription per integration
Don't register many narrow webhooks if a single broad one works. The dispatcher is fan-out per event; subscribing the same URL to 8 events across 8 webhooks is 8x the delivery rate vs 1 webhook with 8 events.
When to split:
- Different integrations consuming different event groups (a fulfillment service vs an analytics pipe).
- Different reliability requirements (a critical pipe vs an experimental
one) so failures in one don't surface as
webhook.failedfor the other.
Updating events
PATCH /v1/admin/webhooks/{id} accepts a new events array — the secret
and URL stay; only the subscription changes. Use this when you want to
listen for additional events without rotating credentials.