> ## Documentation Index
> Fetch the complete documentation index at: https://api-docs.sevalla.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Webhook Events

> Payload structure, event types, signature verification, and retry behavior for Sevalla webhooks.

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:

```json theme={null}
{
  "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](#event-types) below.    |
| `data`              | object | Event-specific payload. Shape depends on `type`.          |

## Request headers

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.

```typescript theme={null}
import { type IncomingMessage } from "node:http";

function verifyWebhook(req: IncomingMessage, secret: string): boolean {
  const signature = req.headers["svl-signature"];
  return signature === secret;
}
```

<Warning>Always verify the `svl-signature` header before processing a webhook. Without verification, anyone who discovers your endpoint URL could send fake events.</Warning>

## 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:**

```json theme={null}
{
  "id": "app-id",
  "company_id": "company-id",
  "name": "my-app",
  "display_name": "My App",
  "location": "eu-west"
}
```

**`APP_DEPLOY` data:**

```json theme={null}
{
  "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:**

```json theme={null}
{
  "id": "site-id",
  "company_id": "company-id",
  "name": "my-site",
  "display_name": "My Site"
}
```

**`STATIC_SITE_DEPLOY` data:**

```json theme={null}
{
  "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.

<Tip>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.</Tip>

## Secret rotation

When you [rotate a webhook secret](/v3/webhooks/roll-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.
