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.
curl -X POST https://api.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"
}'
You do not provide a secret — Entri auto-generates one with crypto.randomBytes(32). The secret field in the response is shown only once.
Response:
{
"_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:
{
"event": "translation.completed",
"timestamp": "2025-03-02T14:30:00.000Z",
"data": {
"keyId": "key_abc123",
"key": "nav.home",
"language": "fr",
"value": "Accueil"
}
}
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:
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);
}
Always use timingSafeEqual when comparing signatures. String equality (===) is vulnerable to timing attacks.
Test a Webhook
Send a test delivery to verify your endpoint is reachable:
curl -X POST https://api.nt3.io/api/organizations/org_789xyz/webhooks/wh_abc123/test \
-H "Cookie: session=..."
The test delivery sends an event with type webhook.test:
{
"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.