Algorithm
-
Read the headers
X-Talario-Timestamp,X-Talario-Delivery-Id,X-Talario-Key-IdandX-Talario-Signature. -
Select the signing secret for
X-Talario-Key-Id. Reject unknown key IDs. - Reject the request if the timestamp differs from your clock by more than 5 minutes. Keep your server clock synchronized (NTP).
- Decode the secret from base64url (without padding) to its 32 raw bytes. Use these bytes as the HMAC key, not the secret string.
-
Build the signed content from the timestamp, the delivery ID and the raw request body, joined by dots:
- Compute HMAC-SHA256 and encode it as lowercase hex.
-
Compare it with the value after
v1=inX-Talario-Signatureusing a constant-time comparison.
Laravel
Store the secret and its key ID in your configuration:config/services.php
app/Http/Middleware/VerifyTalarioWebhook.php
routes/api.php, which has no CSRF protection:
routes/api.php
If your application has no
routes/api.php yet, create it with php artisan install:api. If you register the route in routes/web.php instead, exclude it from CSRF protection in bootstrap/app.php with $middleware->validateCsrfTokens(except: ['webhooks/talario']).Plain PHP
Rotate the secret
In the dashboard, choose Generate new secret for the endpoint. Talario shows the new secret and its new key ID once. The mail servers sign with the new secret as soon as they have applied the change, usually within a minute; attempts already running may still use the previous secret.- Add the new key ID and secret to your configuration, keeping the previous one.
- Generate the new secret in the dashboard and deploy the configuration.
- Remove the previous secret once deliveries arrive with the new key ID.
X-Talario-Key-Id, both secrets work during the change.
Test your endpoint with cURL
Send a signed test request to your endpoint to check your verification. You needopenssl and the secret of the endpoint:
--data-binary, not -d: -d removes line breaks and changes the signed bytes. Your endpoint should answer with 2xx, and with 401 after you change a single character in payload.json without signing again.
A test request with a made-up message ID is not a real message: downloads and API calls for it return
404.