| name | lokalise-webhooks-events |
| description | Implement Lokalise webhook handling and event processing.
Use when setting up webhook endpoints, handling translation events,
or building automation based on Lokalise notifications.
Trigger with phrases like "lokalise webhook", "lokalise events",
"lokalise notifications", "handle lokalise events", "lokalise automation".
|
| allowed-tools | Read, Write, Edit, Bash(curl:*) |
| version | 1.0.0 |
| license | MIT |
| author | Jeremy Longshore <jeremy@intentsolutions.io> |
Lokalise Webhooks & Events
Overview
Handle Lokalise webhooks for real-time translation updates and automation.
Prerequisites
- Lokalise project with webhook access
- HTTPS endpoint accessible from internet
- Understanding of webhook security
- Queue system for reliable processing (optional)
Webhook Event Types
| Event | Trigger | Payload |
|---|
project.imported | File uploaded | File details, key counts |
project.exported | File downloaded | Export details |
project.key.added | New key created | Key data |
project.keys.added | Bulk keys added | Array of keys (max 300) |
project.key.modified | Key updated | Key data with changes |
project.key.deleted | Key removed | Key ID |
project.translation.updated | Translation changed | Translation data |
project.translations.updated | Bulk updates | Array of translations |
project.task.closed | Task completed | Task details |
project.branch.merged | Branch merged | Branch info |
project.contributor.added | User added | Contributor data |
Instructions
Step 1: Set Up Webhook Endpoint
import express from "express";
import crypto from "crypto";
const app = express();
app.post("/webhooks/lokalise",
express.raw({ type: "application/json" }),
async (req, res) => {
const receivedSecret = req.headers["x-secret"] as string;
const expectedSecret = process.env.LOKALISE_WEBHOOK_SECRET!;
if (!verifySecret(receivedSecret, expectedSecret)) {
console.error("Invalid webhook secret");
return res.status(401).json({ error: "Invalid signature" });
}
const event = JSON.parse(req.body.toString());
console.log(`Received event: ${event.event}`);
res.status(200).({ : });
(event);
}
);
(): {
(!received || !expected) ;
crypto.(
.(received),
.(expected)
);
}
Step 2: Event Handler Router
type LokaliseEventType =
| "project.imported"
| "project.exported"
| "project.key.added"
| "project.keys.added"
| "project.key.modified"
| "project.key.deleted"
| "project.translation.updated"
| "project.translations.updated"
| "project.task.closed"
| "project.branch.merged";
interface LokaliseWebhookPayload {
event: LokaliseEventType;
project: {
id: string;
name: string;
};
user?: {
email: string;
full_name: string;
};
action?: string;
[key: string]: any;
}
const eventHandlers: Record<LokaliseEventType, (payload: any) => Promise<void>> = {
"project.imported": handleFileImported,
"project.exported": handleFileExported,
"project.key.added": handleKeyAdded,
"project.keys.added": handleKeysAdded,
"project.key.modified": handleKeyModified,
"project.key.deleted": handleKeyDeleted,
: handleTranslationUpdated,
: handleTranslationsUpdated,
: handleTaskClosed,
: handleBranchMerged,
};
(): <> {
handler = eventHandlers[payload.];
(!handler) {
.();
;
}
{
(payload);
.();
} (error) {
.(, error);
error;
}
}
Step 3: Implement Event Handlers
async function handleFileImported(payload: any): Promise<void> {
const { project, file, import_details } = payload;
console.log(`File imported to ${project.name}: ${file.filename}`);
console.log(`Keys added: ${import_details.keys_added}`);
console.log(`Keys updated: ${import_details.keys_updated}`);
if (import_details.keys_added > 0 || import_details.keys_updated > 0) {
await triggerBuildPipeline(project.id);
}
}
async function handleTranslationUpdated(payload: any): Promise<void> {
const { project, translation, language } = payload;
console.log(`Translation updated in ${project.name}`);
console.log(`Key: ${translation.key_name}, Language: ${language.lang_iso}`);
(
project.,
translation.,
language.
);
}
(): <> {
{ project, task } = payload;
.();
({
: ,
: ,
});
(project., task.);
}
(): <> {
{ project, branch } = payload;
.();
(project.);
}
(): <> {
{ project, keys } = payload;
.();
( key keys) {
(project., key);
}
}
Step 4: Configure Webhook in Lokalise
import { LokaliseApi } from "@lokalise/node-api";
const client = new LokaliseApi({
apiKey: process.env.LOKALISE_API_TOKEN!,
});
async function setupWebhook(projectId: string) {
const webhook = await client.webhooks().create({
project_id: projectId,
url: "https://api.yourapp.com/webhooks/lokalise",
events: [
"project.imported",
"project.translation.updated",
"project.task.closed",
"project.branch.merged",
],
event_lang_map: [
{ event: "project.translation.updated", lang_iso_codes: ["es", "fr", "de"] },
],
});
console.log(`Webhook created with ID: ${webhook.webhook_id}`);
console.log(`Secret: ${webhook.secret}`);
return webhook;
}
Output
- Webhook endpoint receiving events
- Secret verification enabled
- Event handlers for key scenarios
- Async processing with error handling
Error Handling
| Issue | Cause | Solution |
|---|
| Invalid signature | Wrong secret | Verify webhook secret in Lokalise |
| Timeout (8 seconds) | Slow processing | Process async, respond immediately |
| Duplicate events | Retry after failure | Implement idempotency |
| Missing events | Handler not registered | Subscribe to event in Lokalise |
Examples
Idempotent Event Processing
import { Redis } from "ioredis";
const redis = new Redis(process.env.REDIS_URL);
async function processEventIdempotently(
eventId: string,
handler: () => Promise<void>
): Promise<boolean> {
const key = `lokalise:event:${eventId}`;
const acquired = await redis.set(key, "1", "EX", 86400, "NX");
if (!acquired) {
console.log(`Event ${eventId} already processed, skipping`);
return false;
}
await handler();
return true;
}
app.post("/webhooks/lokalise", async (req, res) => {
const event = JSON.parse(req.body);
const eventId = `--`;
res.().({ : });
(eventId,
(event)
);
});
Testing Webhooks Locally
ngrok http 3000
curl -X POST https://your-ngrok-url/webhooks/lokalise \
-H "Content-Type: application/json" \
-H "X-Secret: your-webhook-secret" \
-d '{
"event": "project.translation.updated",
"project": {"id": "123", "name": "Test"},
"translation": {"key_name": "test.key"}
}'
Auto-Rebuild on Translation Update
async function handleTranslationsUpdated(payload: any): Promise<void> {
const { project, translations, action } = payload;
if (action === "import.file" || action === "api.update") {
console.log(`Triggering rebuild for ${translations.length} updated translations`);
await fetch(process.env.VERCEL_DEPLOY_HOOK!, {
method: "POST",
});
}
}
Resources
Next Steps
For performance optimization, see lokalise-performance-tuning.