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 & How Weblate Receives Events
Weblate exposes a generic /hooks/update/ HTTP endpoint that triggers a VCS pull for all components linked to the matching repository URL. When your SCM fires a push event, a CI step or the SCM’s native webhook delivers the repository URL to this endpoint; Weblate then pulls the updated source strings and queues any configured auto-translation jobs.
Core Data Flow:
SCM push event → Webhook delivery (POST /hooks/update/) → Weblate VCS pull → Component scan → Auto-translation queue → Commit translated strings back to VCS → Pipeline notification
Minimal payload (Weblate generic hook):
{
"repository": "https://github.com/org/repo.git"
}
Weblate matches the repository URL to registered components. The optional branch field filters which components update. Unlike SCM-native webhooks (GitHub, GitLab), the generic endpoint does not parse a files_changed list — component file masks are configured in the Weblate admin UI, not in the payload.
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 }}"}
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. 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" \
'{repository: $repo, branch: $branch}')
curl -f -X POST \
-H "Content-Type: application/json" \
-H "X-Gitlab-Event: Push Hook" \
-d "$PAYLOAD" \
"https://weblate.example.com/hooks/update/"
retry: 2
timeout: 5m
Custom 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 tag normalization.
# 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)
# Normalize locale codes in the repository path if present
if "component" in data:
data["component"] = normalize_locale(data["component"])
data["idempotency_key"] = f"{data['repository']}-{data.get('branch', '')}"
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"}
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. A 400 Bad Request indicates the repository URL does not match any registered component. |
| 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 ‘{“repository”:“https://github.com/org/repo.git”}’ |
| 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 is enabled with Fuzzy match threshold ≥ 90%. Ensure the 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).