# Webhooks

Webhooks let external systems subscribe to events that happen in Lathe Studio — test run completions, build creations, release status changes, and more.

**Availability:** Webhooks are available on Pro (up to 5 endpoints) and Enterprise (unlimited endpoints) plans.

---

## [Event Taxonomy](/content/docs/api/webhooks#event-taxonomy/index.html)

All events follow the naming convention: `{resource}.{action}.{version}`

| Event | Trigger | Availability |
| --- | --- | --- |
| `testrun.created.v1` | A new test run is created | Pro+ |
| `testrun.submitted.v1` | A tester submits a test run | Pro+ |
| `testrun.completed.v1` | A test run is marked completed (after build completion) | Pro+ |
| `testcase.failed.v1` | A test case is recorded as failed in an execution | Pro+ |
| `build.created.v1` | A new build is created via API, UI, or webhook | Pro+ |
| `release.released.v1` | A release status changes to `released` | Pro+ |

---

## [Event Payload Structure](/content/docs/api/webhooks#event-payload-structure/index.html)

All events share a common envelope:

```json
{
  "id": "evt_01HXYZ",
  "event": "testrun.submitted.v1",
  "created_at": "2026-03-01T14:32:00Z",
  "data": { ... }
}
```

### `testrun.submitted.v1`

```json
{
  "id": "evt_01HABC",
  "event": "testrun.submitted.v1",
  "created_at": "2026-03-01T14:32:00Z",
  "data": {
    "run_id": "run_01HDEF",
    "project_id": "prj_01HGHI",
    "build_id": "bld_01HJKL",
    "release_id": "rel_01HMNO",
    "submitted_by": "usr_01HPQR",
    "results_summary": {
      "pass": 42,
      "fail": 3,
      "blocked": 1,
      "skipped": 4,
      "total": 50
    }
  }
}
```

### `build.created.v1`

```json
{
  "id": "evt_01HSTU",
  "event": "build.created.v1",
  "created_at": "2026-03-01T12:00:00Z",
  "data": {
    "build_id": "bld_01HVWX",
    "project_id": "prj_01HGHI",
    "name": "v2.4.1",
    "commit_sha": "a1b2c3d4",
    "branch": "main",
    "source": "api"
  }
}
```

Payloads are **self-contained** — they include all the data you need to act without making follow-up API calls.

---

## [Signature Verification](/content/docs/api/webhooks#signature-verification/index.html)

Every webhook delivery includes an `X-Perpetual-Signature` header. Verify this signature before processing the payload to ensure it came from Lathe Studio.

The signature is an HMAC-SHA256 digest of the raw request body, keyed with your webhook secret:

```
X-Perpetual-Signature: sha256=a1b2c3d4e5f6...
```

### Verification Examples

**Node.js:**

```typescript
import crypto from 'crypto'

function verifyWebhookSignature(
  rawBody: string,
  signatureHeader: string,
  secret: string
): boolean {
  const expected = 'sha256=' + crypto
    .createHmac('sha256', secret)
    .update(rawBody)
    .digest('hex')

return crypto.timingSafeEqual(
    Buffer.from(expected),
    Buffer.from(signatureHeader)
  )
}
```

**Python:**

```python
import hmac
import hashlib

def verify_signature(raw_body: bytes, signature_header: str, secret: str) -> bool:
    expected = 'sha256=' + hmac.new(
        secret.encode(), raw_body, hashlib.sha256
    ).hexdigest()
    return hmac.compare_digest(expected, signature_header)
```

Always use a **timing-safe comparison** (`timingSafeEqual` / `hmac.compare_digest`) to prevent timing attacks.

---

## [Registering a Webhook Endpoint](/content/docs/api/webhooks#registering-a-webhook-endpoint/index.html)

Webhook registration is managed in **Organization Settings → Webhooks**.

To register an endpoint:

1. Enter your endpoint URL (must be HTTPS)
2. Select the events to subscribe to
3. Save — a signing secret is generated and shown once

Your endpoint must respond with a `2xx` status within **10 seconds** or the delivery is treated as failed.

---

## [Retry Policy](/content/docs/api/webhooks#retry-policy/index.html)

Failed deliveries (non-2xx response, timeout, or connection error) are retried with exponential backoff:

| Attempt | Delay |
| --- | --- |
| 1 (initial) | Immediate |
| 2 | 5 minutes |
| 3 | 30 minutes |
| 4 | 2 hours |
| 5 | 12 hours |

After 5 failed attempts, the event is sent to the **dead-letter queue** and delivery is stopped. You can view and replay dead-letter events from the Webhooks settings page.

---

## [Developer Skill](/content/docs/api/webhooks#developer-skill/index.html)

Download the [Lathe Studio Developer Skill](/content/skills/lathe-api-developer/index.html) for copy-paste signature verification examples, retry schedules, and CI/CD integration recipes in Node.js, Python, and Go.

---

## [Best Practices](/content/docs/api/webhooks#best-practices/index.html)

- **Respond immediately, process asynchronously.** Return `200 OK` immediately and process the payload in a background job to avoid timeouts.
- **Handle duplicate deliveries.** Use `evt.id` to deduplicate — the same event may be delivered more than once after retries.
- **Verify signatures.** Never process a webhook without first verifying the signature.
- **Monitor the dead-letter queue.** Check failed deliveries periodically in Org Settings.
