API webhooks
Storlaunch sends webhook events to URLs you control so you can react to state changes in real time — products created, checkouts completed, subscriptions renewed.
This page is the API reference for webhooks: the envelope, the signature scheme, the retry policy. For the per-event catalogue, see Webhook events. For programmatic endpoint management, see Webhook endpoints.
The envelope
Every webhook is a POST to your endpoint with this body:
{
"id": "evt_01HX...",
"type": "checkout.completed",
"createdAt": "2026-05-13T10:43:22Z",
"accountId": "acc_01HX...",
"data": { /* event-type-specific payload */ }
}
| Field | Description |
|---|---|
id |
Unique event ID. Stable across retries; use it for idempotency on your side. |
type |
Event type (checkout.completed, storlaunch.product.created.v1, ...). |
createdAt |
ISO 8601 UTC timestamp when the event was emitted. |
accountId |
The workspace this event is for. |
data |
Event-specific payload — see the per-event reference pages. |
Headers
| Header | Value |
|---|---|
Content-Type |
application/json |
X-Storlaunch-Signature |
HMAC signature header (see below). |
X-Storlaunch-Event-Id |
Same as event.id — handy for log correlation. |
X-Storlaunch-Delivery-Id |
Per-attempt ID; different on each retry of the same event. |
Signature scheme
Every webhook is signed so you can verify it came from Storlaunch.
Header format
X-Storlaunch-Signature: t=1715593403,v1=ab12cd34...
t= Unix epoch seconds at signing time.v1= hexHMAC-SHA256(<webhook signing secret>, <t>.<raw body>).
Verification recipe
- Split the header on
,then=to extracttandv1. - Compute the expected signature using your webhook's signing secret (returned at endpoint create time).
- Compare in constant time.
- Reject events older than 5 minutes (
now - t > 300) to prevent replay.
// Node
import crypto from 'node:crypto';
function verifyWebhook(rawBody, header, secret, tolerance = 300) {
const parts = Object.fromEntries(header.split(',').map(s => s.split('=')));
const t = parseInt(parts.t, 10);
if (Math.abs(Date.now() / 1000 - t) > tolerance) {
throw new Error('Signature timestamp out of tolerance');
}
const expected = crypto.createHmac('sha256', secret)
.update(`${t}.${rawBody}`).digest('hex');
if (!crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1))) {
throw new Error('Signature mismatch');
}
return JSON.parse(rawBody);
}
# Python
import hmac, hashlib, time, json
def verify_webhook(raw_body: bytes, header: str, secret: str, tolerance: int = 300):
parts = dict(s.split("=") for s in header.split(","))
t = int(parts["t"])
if abs(time.time() - t) > tolerance:
raise ValueError("Signature timestamp out of tolerance")
expected = hmac.new(secret.encode(), f"{t}.{raw_body.decode()}".encode(), hashlib.sha256).hexdigest()
if not hmac.compare_digest(expected, parts["v1"]):
raise ValueError("Signature mismatch")
return json.loads(raw_body)
// Go
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"errors"
"fmt"
"strings"
"time"
)
func VerifyWebhook(rawBody []byte, header, secret string, tolerance time.Duration) error {
parts := map[string]string{}
for _, kv := range strings.Split(header, ",") {
x := strings.SplitN(kv, "=", 2)
if len(x) == 2 { parts[x[0]] = x[1] }
}
t, _ := time.Parse(time.RFC3339, parts["t"])
if time.Since(t) > tolerance { return errors.New("signature timestamp out of tolerance") }
mac := hmac.New(sha256.New, []byte(secret))
mac.Write([]byte(fmt.Sprintf("%d.%s", t.Unix(), rawBody)))
expected := hex.EncodeToString(mac.Sum(nil))
if !hmac.Equal([]byte(expected), []byte(parts["v1"])) {
return errors.New("signature mismatch")
}
return nil
}
Each SDK exports a verifyWebhook helper that does all this for you. See SDK.
Retry policy
We deliver events with at-least-once semantics:
- Initial attempt — immediately after the event is emitted.
- Retries — exponential backoff at T+1m, T+5m, T+30m, T+2h, T+8h, T+24h (six attempts after the initial).
- Success is any 2xx response. 4xx and 5xx both trigger retry.
- Disable threshold — 20 consecutive failures auto-disables the endpoint (you'll see
active: falseinGET /v1/payment/webhook-endpoints). Re-enable manually after fixing.
The total retry window is roughly 35 hours. Beyond that, the event is permanently dropped.
Subscribing to events
In Settings → Webhooks (or via Webhook endpoints API):
- Add your endpoint URL (HTTPS only).
- Pick the events you want. Leave empty to subscribe to everything.
- Copy the signing secret — you only see it once.
Adding an event type later affects only future deliveries; we don't backfill historical events.
Idempotency
Use event.id as your idempotency key. A typical handler:
// Node
app.post('/webhooks/storlaunch', async (req, res) => {
const event = verifyWebhook(req.rawBody, req.headers['x-storlaunch-signature'], SECRET);
const seen = await db.events.exists({ id: event.id });
if (seen) return res.status(200).end();
await processEvent(event);
await db.events.insert({ id: event.id, type: event.type, receivedAt: Date.now() });
res.status(200).end();
});
Replay protection
The signature scheme includes a timestamp; reject events older than 5 minutes. Otherwise an attacker who once captures a valid event could replay it indefinitely.
Test events
The dashboard's Webhooks → Send test event action delivers a synthetic event to your endpoint — useful for verifying signature handling before going live. The test event's id is prefixed evt_test_ so you can filter it out of production processing.
Next
- Webhook events — the full catalogue.
- Webhook endpoints — manage endpoints programmatically.
- API keys — the other half of the integration credential.