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

# Webhooks

> Manage webhook subscriptions for real-time events.

**Webhooks** let you receive real-time HTTP notifications when events occur in Entri. Instead of polling the API to check for changes, you register a URL and Entri will POST a JSON payload to it whenever a subscribed event fires.

Webhooks are useful for triggering downstream workflows — for example, automatically pulling translations into your codebase when a language reaches 100% completion, or sending a Slack notification when an import finishes.

## Supported Events

| Event                   | Description                                    |
| ----------------------- | ---------------------------------------------- |
| `key.created`           | A translation key was created.                 |
| `key.deleted`           | A translation key was deleted.                 |
| `translation.completed` | A translation was saved (source: human or AI). |
| `language.completed`    | All keys in a language have been translated.   |
| `import.completed`      | A file import job finished.                    |
| `export.completed`      | A file export was generated.                   |

## Endpoints

**Organization-level webhooks**

```
POST   /api/organizations/:orgId/webhooks                   Create a webhook
GET    /api/organizations/:orgId/webhooks                   List webhooks
PATCH  /api/organizations/:orgId/webhooks/:webhookId        Update a webhook
DELETE /api/organizations/:orgId/webhooks/:webhookId        Delete a webhook
POST   /api/organizations/:orgId/webhooks/:webhookId/test   Send a test payload
```

**Project-level webhooks**

```
POST   /api/projects/:projectId/webhooks                    Create a project webhook
GET    /api/projects/:projectId/webhooks                    List project webhooks
PATCH  /api/projects/:projectId/webhooks/:webhookId         Update a project webhook
DELETE /api/projects/:projectId/webhooks/:webhookId         Delete a project webhook
```

## Create a Webhook

When you create a webhook, Entri generates a signing secret for you. **The secret is returned only once** in the creation response — store it immediately in a secure location.

```bash theme={null}
curl -X POST https://app.nt3.io/api/organizations/org_789xyz/webhooks \
  -H "Cookie: session=..." \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://your-app.example.com/webhooks/entri",
    "events": ["translation.completed", "language.completed", "import.completed"],
    "projectId": "proj_6abc123def456",
    "description": "Notify CI/CD pipeline"
  }'
```

<Note>
  You do **not** provide a secret — Entri auto-generates one with `crypto.randomBytes(32)`. The `secret` field in the response is shown only once.
</Note>

**Response:**

```json theme={null}
{
  "_id": "wh_abc123",
  "url": "https://your-app.example.com/webhooks/entri",
  "events": ["translation.completed", "language.completed", "import.completed"],
  "projectId": "proj_6abc123def456",
  "description": "Notify CI/CD pipeline",
  "active": true,
  "secret": "a1b2c3d4e5f6...",
  "failureCount": 0,
  "created": "2025-03-01T10:00:00.000Z"
}
```

## Webhook Payload Structure

Every webhook POST has a JSON body with consistent structure:

```json theme={null}
{
  "event": "translation.completed",
  "timestamp": "2025-03-02T14:30:00.000Z",
  "data": {
    "keyId": "key_abc123",
    "key": "nav.home",
    "language": "fr",
    "value": "Accueil"
  }
}
```

## Request Headers

Each webhook delivery includes these headers:

| Header                | Description                                                            |
| --------------------- | ---------------------------------------------------------------------- |
| `Content-Type`        | `application/json`                                                     |
| `X-Webhook-Signature` | HMAC-SHA256 hex signature of the request body (see Verification below) |
| `X-Webhook-Event`     | The event type (e.g. `translation.completed`)                          |

## Payload Verification

Entri signs every webhook request with an HMAC-SHA256 signature using the auto-generated webhook secret. The signature is included in the `X-Webhook-Signature` header as a plain hex string (no prefix):

```
X-Webhook-Signature: a1b2c3d4e5f6...
```

Verify the signature in your receiver before processing the payload:

```javascript theme={null}
import { createHmac, timingSafeEqual } from "node:crypto";

function verifyWebhook(rawBody, signature, secret) {
  const expected = createHmac("sha256", secret)
    .update(rawBody)
    .digest("hex");

  const expectedBuffer = Buffer.from(expected);
  const signatureBuffer = Buffer.from(signature);

  if (expectedBuffer.length !== signatureBuffer.length) return false;
  return timingSafeEqual(expectedBuffer, signatureBuffer);
}
```

<Warning>
  Always use `timingSafeEqual` when comparing signatures. String equality (`===`) is vulnerable to timing attacks.
</Warning>

## Test a Webhook

Send a test delivery to verify your endpoint is reachable:

```bash theme={null}
curl -X POST https://app.nt3.io/api/organizations/org_789xyz/webhooks/wh_abc123/test \
  -H "Cookie: session=..."
```

The test delivery sends an event with type `webhook.test`:

```json theme={null}
{
  "event": "webhook.test",
  "timestamp": "2025-03-02T14:30:00.000Z",
  "data": {
    "message": "This is a test webhook delivery"
  }
}
```

## Key Notes

* Your endpoint must respond with a `2xx` status code within 10 seconds to be considered a successful delivery.
* Failed deliveries are not automatically retried. Monitor `failureCount` on the webhook object and investigate delivery failures promptly.
* Webhooks can be scoped to a specific project by setting `projectId`, or created at the organization level to receive events across all projects.
