Configuring Weblate Webhooks for Auto-Translation
Introduction to Event-Driven Localization
Modern i18n pipelines require seamless synchronization between code deployments and translation updates. Manual string extraction and periodic batch updates create bottlenecks in agile release cycles, leading to stale localization files, inconsistent UI copy across environments, and delayed feature rollouts for international markets. Teams relying on scheduled syncs frequently encounter merge conflicts and context drift when source strings change between translation cycles. Implementing a robust Weblate Self-Hosted Setup eliminates manual intervention by bridging your version control system with Weblate’s translation engine, reducing context switch overhead for engineering and localization teams.
Webhook Architecture & Payload Structure
The webhook listener expects a standardized JSON payload containing repository metadata, branch references, and component identifiers. Proper payload formatting ensures the translation queue processes only relevant files, reducing unnecessary machine translation (MT) API calls and preventing translation memory pollution.
Core Data Flow:
SCM Event → Webhook Payload → Weblate Hook Receiver (/hooks/update/) → Component Scan → Auto-Translation Execution → Commit Back to VCS → Pipeline Notification
Required Payload Schema:
{
"repository": "https://github.com/org/repo.git",
"branch": "main",
"component": "frontend/locales",
"operation": "push",
"files_changed": ["locales/en.json", "locales/fr.json"]
}
Weblate’s /hooks/update/ endpoint consumes this schema, maps it to the configured VCS component, and queues the auto-translation engine. Ensure the payload strictly adheres to this structure to bypass schema validation rejections.
CI/CD Pipeline Integration
Embedding webhook triggers into your Translation Workflows & CI/CD Pipeline Sync strategy guarantees that localization keeps pace with feature development. Configure pipeline stages to dispatch hooks only after successful linting, unit testing, and build validation. Triggering auto-translation on broken or incomplete source strings corrupts translation memory and forces manual cleanup.
Pipeline Guardrails:
- Run
i18n-extractorformatjs compileas a pre-hook validation step. - Filter webhook dispatches to
paths-ignore: ['*.md', 'docs/**', 'tests/**']. - Enforce branch protection: only
main,release/*, orl10n/*branches may trigger auto-translation.
Framework-Specific Implementation
GitHub Actions
Use push or workflow_dispatch triggers to POST to the Weblate webhook URL. Implement HMAC-SHA256 signature verification and exponential backoff for concurrent pushes.
name: Trigger Weblate Auto-Translation
on:
push:
branches: [main, release/*]
paths-ignore:
- '**/*.md'
- 'docs/**'
jobs:
weblate-sync:
runs-on: ubuntu-latest
steps:
- name: Generate HMAC Signature
id: hmac
run: |
PAYLOAD=$(cat <<EOF
{"repository":"${{ github.repositoryUrl }}","branch":"${{ github.ref_name }}","component":"frontend/locales","operation":"push"}
EOF
)
SIGNATURE=$(echo -n "$PAYLOAD" | openssl dgst -sha256 -hmac "${{ secrets.WEBHOOK_SECRET }}" | awk '{print $2}')
echo "signature=$SIGNATURE" >> $GITHUB_OUTPUT
echo "$PAYLOAD" > /tmp/payload.json
- name: Dispatch to Weblate
uses: fjogeleit/http-request-action@v1
with:
url: 'https://weblate.example.com/hooks/update/'
method: 'POST'
customHeaders: '{"X-Hub-Signature-256": "sha256=${{ steps.hmac.outputs.signature }}", "Content-Type": "application/json"}'
data: '@${{ github.workspace }}/payload.json'
timeout: 30000
retry: 3
retryDelay: 1000
GitLab CI
Configure pipeline webhooks with custom JSON payload mapping. Utilize CI_JOB_TOKEN for authenticated API calls when bypassing public webhook exposure. Restrict execution to default branch pipelines.
trigger_weblate:
stage: deploy
image: curlimages/curl:latest
rules:
- if: '$CI_PIPELINE_SOURCE == "push" && $CI_COMMIT_BRANCH == "main"'
script:
- |
PAYLOAD=$(jq -n \
--arg repo "$CI_REPOSITORY_URL" \
--arg branch "$CI_COMMIT_BRANCH" \
--arg comp "frontend/locales" \
'{repository: $repo, branch: $branch, component: $comp, operation: "push"}')
curl -f -X POST \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $CI_JOB_TOKEN" \
-H "X-Gitlab-Event: Push Hook" \
-d "$PAYLOAD" \
"https://weblate.example.com/hooks/update/"
retry: 2
timeout: 5m
Custom Node.js/Python Proxy Middleware
Deploy a lightweight proxy to validate signatures, normalize locale codes (e.g., zh-CN → zh_Hans), and enforce idempotency. Prevents duplicate translation jobs and handles BCP 47 fallbacks.
# proxy_server.py (FastAPI)
from fastapi import FastAPI, Request, HTTPException, Header
import hashlib, hmac, json, os
from httpx import AsyncClient
app = FastAPI()
WEBLATE_URL = os.getenv("WEBLATE_URL")
SHARED_SECRET = os.getenv("WEBHOOK_SECRET").encode()
def verify_hmac(payload: bytes, signature: str) -> bool:
expected = hmac.new(SHARED_SECRET, payload, hashlib.sha256).hexdigest()
return hmac.compare_digest(f"sha256={expected}", signature)
def normalize_locale(tag: str) -> str:
mapping = {"zh-CN": "zh_Hans", "zh-TW": "zh_Hant", "pt-BR": "pt_BR"}
return mapping.get(tag, tag.replace("-", "_"))
@app.post("/webhook/forward")
async def forward_to_weblate(request: Request, x_signature: str = Header(None)):
payload = await request.body()
if not verify_hmac(payload, x_signature):
raise HTTPException(status_code=403, detail="Invalid HMAC signature")
data = json.loads(payload)
data["component"] = normalize_locale(data.get("component", ""))
data["idempotency_key"] = f"{data['repository']}-{data['branch']}-{data.get('operation')}"
async with AsyncClient() as client:
resp = await client.post(f"{WEBLATE_URL}/hooks/update/", json=data)
if resp.status_code != 200:
raise HTTPException(status_code=resp.status_code, detail=resp.text)
return {"status": "queued", "weblate_response": resp.json()}
Debugging & Troubleshooting Common Edge Cases
| Step | Action | Expected Outcome & Verification |
|---|---|---|
| 1 | Verify webhook delivery status in SCM dashboard | HTTP 200 OK from Weblate /hooks/update/. Check SCM delivery logs for exact payload and response headers. |
| 2 | Inspect Weblate audit log for hook processing errors | Navigate to Admin → Audit Log. Filter by Hook or Auto-translation. Status must show completed or queued. Look for 400 Bad Request indicating malformed JSON. |
| 3 | Validate HMAC signature mismatch | Ensure the shared secret matches exactly. Strip trailing newlines/whitespace from environment variables. Verify hash algorithm alignment (sha256 vs sha1). Test locally: `echo -n ‘{“test”:true}’ |
| 4 | Check auto-translation component settings | In Weblate UI, verify: (a) MT services are active and quota-available, (b) Source/Target language mapping matches BCP 47 tags, © Auto translate checkbox is enabled with Fuzzy match threshold ≥ 90%. Ensure component is not locked by concurrent merge operations. |
Proactive Logging Strategy: Implement structured JSON logging at the webhook receiver level. Cross-reference request_id across SCM delivery logs, proxy middleware, and Weblate audit trails for precise failure attribution. When MT quota exhaustion occurs, configure fallback to glossary-only matching or queue retry with exponential backoff (2^n * 1000ms).