· 6 min read

Real-Time Translation Updates with Webhooks

You publish a translation in LangSyncer. How does your Laravel app know?

Webhooks.

No redeploy, no cron job hammering an API, no waiting for a cache TTL to expire. LangSyncer calls your app the moment something changes, your app refreshes its translation cache, and users see the new copy on their next request. Here's how the flow works, how to set it up, and what a webhook handler looks like when you want to build your own workflows on top.

The Problem in a Real Laravel App

With file-based translations, shipping a copy change means running your deploy pipeline:

# somewhere in deploy.sh
php artisan translator:sync
php artisan cache:clear

That's fine for code. It's overkill for changing "Sign up" to "Get started" in Spanish. The usual workaround is polling on a schedule:

// routes/console.php
Schedule::command('translator:sync')->everyMinute();

But polling has real costs:

  • Wastes resources (99% of polls find nothing)
  • Delays updates by up to 60 seconds
  • Counts against rate limits

Webhooks invert the model. Instead of your app asking "anything new?" thousands of times a day, LangSyncer tells it exactly once, exactly when there's something to fetch. The content team updates a translation at 2:00 PM, and users see it at 2:00 PM. Nobody files a ticket, nobody waits for the Friday deploy.

The Flow

  1. You click Publish in LangSyncer
  2. LangSyncer sends a webhook to your app
  3. Your app invalidates its translation cache
  4. Next request fetches fresh translations
  5. Users see the update

Once the webhook lands, fresh translations are live within seconds. No deployment. No manual cache clear.

Setting It Up

  1. Enable live mode in your .env. The translator-client package does the heavy lifting (install steps are in the getting started guide):

    CLI_TRANSLATOR_CLIENT_MODE=live
    CLI_TRANSLATOR_CLIENT_WEBHOOK_ENABLED=true
    

    The package registers a webhook endpoint automatically at /api/translator/webhook. Want a different path? Set CLI_TRANSLATOR_CLIENT_WEBHOOK_ROUTE.

  2. Register the webhook in LangSyncer. Open your project's Actions modal, go to the Webhooks tab, click Add Webhook, enter your endpoint URL (it must be HTTPS), and select the events you care about:

    | Event | When It Fires | |-------|---------------| | Translations Published | Batch publish (most common) | | Single Translation Updated | Individual edit | | Single Translation Deleted | Individual delete | | Bulk Translations Updated | Bulk operations | | Project Languages Changed | Language config changes |

    Most apps only need Translations Published, and it's the one required for live mode to work. Each webhook gets its own secret key, which the translator-client package uses automatically to verify that requests really came from LangSyncer.

  3. Know what arrives. A single-edit event like translation.updated delivers a JSON payload identifying exactly what changed. The shape looks like this (illustrative, check your delivery logs for the exact payload your project receives):

    {
        "event": "translation.updated",
        "locale": "es",
        "group": "messages",
        "key": "welcome",
        "value": "Bienvenido a nuestra aplicación",
        "old_value": "Bienvenido",
        "timestamp": "2026-07-10T14:02:11+00:00"
    }
    

    With the translator-client package, you never touch this directly. It verifies the signature and refreshes the cache for you.

  4. Or build your own handler. Point a second webhook at a custom endpoint when you want extra behavior: purge a CDN, ping Slack, log copy changes. A minimal illustrative handler:

    // routes/api.php
    Route::post('/webhooks/translations', function (Request $request) {
        // Verify the HMAC signature with the webhook's secret
        // (the translator-client package does this automatically
        // on its own endpoint)
        $expected = hash_hmac(
            'sha256',
            $request->getContent(),
            config('services.langsyncer.webhook_secret')
        );
    
        abort_unless(
            hash_equals($expected, $request->header('X-Translator-Signature', '')),
            403
        );
    
        // Respond fast, do the work in a queue
        ProcessTranslationEvent::dispatch($request->json()->all());
    
        return response()->noContent();
    });
    

    And the queued job:

    class ProcessTranslationEvent implements ShouldQueue
    {
        public function __construct(public array $payload) {}
    
        public function handle(): void
        {
            if ($this->payload['event'] === 'translations.published') {
                // e.g. purge an edge cache, notify the team, warm caches
                Cache::forget('translator.'.$this->payload['locale'] ?? '');
            }
        }
    }
    

    The pattern matters more than the specifics: verify the signature, return a 2xx immediately, and push real work onto a queue so slow jobs never cause delivery timeouts.

  5. Verify deliveries. Every webhook has delivery logs in LangSyncer showing the timestamp, event type, response code, and response time. On the app side, php artisan translator:status shows your webhook status alongside mode, API key, and cache settings. Failed delivery? Check the logs; transient failures are retried automatically, up to 3 times with exponential backoff.

Webhooks and the Draft-to-Publish Workflow

Webhooks pair naturally with LangSyncer's versioning system. Edits save as drafts, and drafts never trigger the translations.published event, so your content team can revise a page's worth of copy over a morning without spraying half-finished strings at production. When they hit Publish, all drafts go active at once, CDN files regenerate once, and one webhook tells your app to refresh.

The practical upshot: batch your changes. Ten edits published together means one cache invalidation instead of ten, and one entry in your delivery logs to check instead of ten. Add release notes when you publish, and the publication record plus the webhook log give you a clean audit trail of what changed and when your app found out.

When Not to Use Webhooks

  • Static sites with scheduled deploys. If translations change rarely and you deploy weekly anyway, static mode plus translator:sync in your deploy script is simpler and needs no publicly reachable endpoint. See Live vs Static Mode for the full decision.
  • Local development. Your laptop isn't reachable from the internet, so test webhook delivery through a tunnel like ngrok before assuming something is broken.
  • To-the-second synchronization. Publishing regenerates CDN files and then dispatches webhooks a couple of minutes later. That's near-real-time and perfect for copy changes, but don't build a feature-flag system on top of it.

Gotchas

  • Respond fast with a 200. Anything else counts as a failed delivery and triggers a retry, so queue heavy work instead of doing it inline.
  • Repeated failures deactivate the webhook automatically. If updates stop arriving, check the delivery logs, fix your endpoint, and re-enable the webhook from the Webhooks tab. The troubleshooting guide covers the live-mode checklist.
  • The endpoint URL must be HTTPS, and the secret is what makes signature verification meaningful. Keep it out of version control.

Related Posts


Configure webhooks →

We use cookies to improve your experience and analyze site traffic. Cookie Policy

Cookie Preferences

Essential

Required for the site to work

Analytics

Help us improve the site

Marketing

Personalized ads and content