API
Webhooks
Register a callback URL to receive simulation lifecycle events as they happen. Each delivery is signed with HMAC-SHA256 against a per-webhook secret you control. Drop polling.
Customer subscriptions
Six endpoints manage subscriptions. Every call requires a Bearer key with the listed scope; cross-org reads return 404 (never 403) so the API surface never leaks the existence of another org's webhooks.
| Method | Path | Scope | Status | Summary |
|---|---|---|---|---|
| POST | /v1/webhooks | webhooks:write | 201 | Create a subscription. Raw signing secret returned ONCE. |
| GET | /v1/webhooks | webhooks:read | 200 | Cursor-paginated list (max 100/page), newest first. |
| GET | /v1/webhooks/{webhook_id} | webhooks:read | 200 / 404 | Fetch one subscription. Cross-org reads return 404. |
| PATCH | /v1/webhooks/{webhook_id} | webhooks:write | 200 / 404 | Partial update. Empty body is a no-op. |
| DELETE | /v1/webhooks/{webhook_id} | webhooks:write | 200 / 404 | Hard-delete. Re-deleting a missing row returns 404. |
| POST | /v1/webhooks/{webhook_id}/rotate-secret | webhooks:write | 201 | Mint a new signing secret. Old secret invalid immediately. |
Creating a subscription
POST creates require an Idempotency-Key header (UUID) per CLAUDE.md API hard rules. URLs must be HTTPS — plain http:// is rejected on every environment, and localhost variants are rejected on production. The response body includes the raw signing secret EXACTLY ONCE — save it now.
curl -X POST https://api.simulix.com/v1/webhooks \
-H "Authorization: Bearer $SIMULIX_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"url": "https://example.com/webhooks/simulix",
"events": ["simulation.completed", "simulation.failed"],
"enabled": true
}'Pass ["*"] to subscribe to all events. Lost the secret? POST to /v1/webhooks/{webhook_id}/rotate-secret for a fresh one — the old secret is invalidated immediately.
Signature header
Every delivery carries an X-Simulix-Signature header in Stripe's wire format:
X-Simulix-Signature: t=<unix_timestamp>,v1=<hmac_sha256_hex>
The HMAC input is f"{timestamp}." + raw_body_bytes, computed with HMAC-SHA256 against your raw secret. Receivers MUST reject signatures whose timestamp is older than 5 minutes (replay defense). The Simulix SDK exposes a verify_signature helper that round-trips the math constant-time and enforces the freshness window.
Rotating the secret
There is no grace period. The moment the rotate-secret response goes out, the old secret no longer signs valid deliveries. Roll forward by deploying the new secret to your receiver before invoking rotate; if a delivery fails verification with the old secret your receiver should retry, then rotate again if needed. Same trade as the API key rotate flow.
Delivery history
GET /v1/webhooks/{webhook_id}/deliveries returns the most-recent delivery attempts for one webhook in your organization. Useful when a customer endpoint was down for a window and you want to see which events were retried, which were marked permanently failed, and what HTTP status the receiver returned on the last attempt. Default limit is 20, max 100. Each row includes attempt count, last response status (or null on transport error), and the next-retry timestamp if the row is still pending. The same data also renders at /dashboard/webhooks/{id}/deliveries for an in-browser view. Cross-org isolation is enforced — the endpoint returns 404 (not 403) for a webhook id that exists but belongs to a different organization, so probing for IDs leaks nothing.
Stripe receiver (internal)
POST /v1/webhooks/stripe is internal-only — the Stripe webhook receiver, not a customer-callable surface. It authenticates via the Stripe-Signature HMAC against the raw request body (no Bearer key path) and handles checkout.session.completed, customer.subscription.updated, and customer.subscription.deleted. Refer to Stripe's event documentation for the canonical payload reference. For the customer-facing billing flow, see /docs/billing.