When an event fires, Sevalla sends a POST request to your webhook endpoint with a JSON payload. This page documents what your endpoint receives and how to verify it.
Payload structure
Every webhook request body follows this format:
{
"webhook_id": "d5f8a1c2-3b4e-5f6a-7b8c-9d0e1f2a3b4c",
"event_id": "a1b2c3d4-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"event_delivery_id": "f1e2d3c4-b5a6-9788-9a0b-c1d2e3f4a5b6",
"type": "APP_DEPLOY",
"data": {}
}
| Field | Type | Description |
|---|
webhook_id | string | UUID of the webhook that triggered this delivery. |
event_id | string | UUID of the event. Same across retries. |
event_delivery_id | string | UUID of this specific delivery attempt. Unique per retry. |
type | string | The event type. See Event types below. |
data | object | Event-specific payload. Shape depends on type. |
Sevalla sends two headers with every webhook request:
| Header | Value |
|---|
content-type | application/json |
svl-signature | Your webhook secret (e.g. whsec_...). |
Verifying signatures
Every webhook request includes an svl-signature header containing your webhook’s secret. Compare this value against the secret stored on your end to verify the request is from Sevalla.
import { type IncomingMessage } from "node:http";
function verifyWebhook(req: IncomingMessage, secret: string): boolean {
const signature = req.headers["svl-signature"];
return signature === secret;
}
Always verify the svl-signature header before processing a webhook. Without verification, anyone who discovers your endpoint URL could send fake events.
Event types
You can subscribe to any combination of these events when creating a webhook.
Application events
| Type | Fires when |
|---|
APP_CREATE | An application is created. |
APP_UPDATE | An application is modified. |
APP_DELETE | An application is deleted. |
APP_DEPLOY | A deployment starts, succeeds, or fails. |
APP_CREATE, APP_UPDATE, APP_DELETE data:
{
"id": "app-id",
"company_id": "company-id",
"name": "my-app",
"display_name": "My App",
"location": "eu-west"
}
APP_DEPLOY data:
{
"company_id": "company-id",
"app_id": "app-id",
"app_name": "my-app",
"app_display_name": "My App",
"deployment_id": "deploy-id",
"status": "success",
"commit": "a1b2c3d",
"stage": "production",
"is_preview": false
}
Static site events
| Type | Fires when |
|---|
STATIC_SITE_CREATE | A static site is created. |
STATIC_SITE_UPDATE | A static site is modified. |
STATIC_SITE_DELETE | A static site is deleted. |
STATIC_SITE_DEPLOY | A static site deployment starts, succeeds, or fails. |
STATIC_SITE_CREATE, STATIC_SITE_UPDATE, STATIC_SITE_DELETE data:
{
"id": "site-id",
"company_id": "company-id",
"name": "my-site",
"display_name": "My Site"
}
STATIC_SITE_DEPLOY data:
{
"company_id": "company-id",
"static_site_id": "site-id",
"static_site_name": "my-site",
"static_site_display_name": "My Site",
"deployment_id": "deploy-id",
"status": "success",
"repo_url": "https://github.com/org/repo",
"branch": "main",
"commit": "a1b2c3d",
"is_preview": false
}
Database events
| Type | Fires when |
|---|
DATABASE_CREATE | A database is created. |
DATABASE_UPDATE | A database is modified. |
DATABASE_DELETE | A database is deleted. |
Retry behavior
If your endpoint returns a non-2xx status code or doesn’t respond, Sevalla retries the delivery up to 12 times with exponential backoff:
| Retry | Delay |
|---|
| 1 | 1 minute |
| 2 | 5 minutes |
| 3 | 10 minutes |
| 4 | 30 minutes |
| 5 | 1 hour |
| 6 | 2 hours |
| 7 | 4 hours |
| 8 | 8 hours |
| 9 | 16 hours |
| 10 | 1 day |
| 11 | 2 days |
| 12 | 3 days |
Each retry generates a new event_delivery_id but keeps the same event_id. After 12 failed attempts, the delivery is marked as failed permanently.
Return a 200 status code as quickly as possible. Process the webhook payload asynchronously to avoid timeouts. If your endpoint is slow to respond, it may be treated as a failure.
Secret rotation
When you rotate a webhook secret, you can set a grace period (0-24 hours) during which both the old and new secrets are valid. During this window, the svl-signature header will contain the old secret. Once the grace period expires, only the new secret is used.
This lets you deploy updated verification logic without missing events.