Signature Verification
Every webhook delivery is signed, so you can confirm it actually came from CELITECH before acting on it. Never process a webhook payload without verifying its signature first.
Your Signing Secret
Each endpoint has its own signing secret, available under Developers → Webhooks in the dashboard. Treat it like any other credential — keep it out of client-side code, logs, and version control. If a secret is ever compromised, rotate it from the same page — just remember to update your verification code with the new secret afterward.
Request Headers
Every delivery includes the following headers:
| Header | Description |
|---|---|
svix-id | A unique identifier for this delivery. Every retry attempt of the same delivery carries this same identifier — use it to de-duplicate if you receive it more than once. |
svix-timestamp | The Unix timestamp (in seconds) the delivery was sent. |
svix-signature | One or more space-delimited signatures for this delivery, used to verify authenticity. |
Verifying the Signature
Verification works by computing an HMAC-SHA256 signature over the delivery's svix-id, svix-timestamp, and raw body using your signing secret, then comparing it — with a constant-time comparison, to avoid leaking timing information — against the value in svix-signature. Requests with a svix-timestamp too far in the past or future should also be rejected, to protect against replay attacks.
An official verification library, available for most languages, handles all of this for you — see Example Verification Code below. If the signature doesn't verify, reject the request (e.g. with a 401) and don't process the payload.
Always pass the raw, unparsed request body to the verification library. Re-serializing a parsed JSON object before verifying can produce a different byte sequence and cause verification to fail incorrectly.
Example Verification Code
Use an official verification library, available for most languages — it implements the algorithm described above for you.
- JavaScript
- TypeScript
- PHP
- Java
- Go
- Python
- C#
Using an official verification library (npm install svix):
import { Webhook } from 'svix';
function verifyWebhookSignature(payload, headers, secret) {
const wh = new Webhook(secret);
try {
wh.verify(payload, headers); // headers: { 'svix-id', 'svix-timestamp', 'svix-signature' }
return true;
} catch {
return false;
}
}
Using an official verification library (npm install svix):
import { Webhook } from 'svix';
function verifyWebhookSignature(payload: string, headers: Record<string, string>, secret: string): boolean {
const wh = new Webhook(secret);
try {
wh.verify(payload, headers); // headers: { 'svix-id', 'svix-timestamp', 'svix-signature' }
return true;
} catch {
return false;
}
}
Using an official verification library (composer require svix/svix):
use Svix\Webhook;
function verifyWebhookSignature($payload, $headers, $secret) {
$wh = new Webhook($secret);
try {
$wh->verify($payload, $headers); // $headers: ['svix-id' => ..., 'svix-timestamp' => ..., 'svix-signature' => ...]
return true;
} catch (\Exception $e) {
return false;
}
}
Using an official verification library (Gradle: implementation "com.svix:svix:0.x.y"):
import com.svix.Webhook;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
boolean verifyWebhookSignature(String payload, String svixId, String svixTimestamp, String svixSignature, String secret) {
try {
Webhook wh = new Webhook(secret);
Map<String, List<String>> headers = new HashMap<>();
headers.put("svix-id", List.of(svixId));
headers.put("svix-timestamp", List.of(svixTimestamp));
headers.put("svix-signature", List.of(svixSignature));
wh.verify(payload, headers);
return true;
} catch (Exception e) {
return false;
}
}
Using an official verification library (go get github.com/svix/svix-webhooks/go):
import svix "github.com/svix/svix-webhooks/go"
// headers must have svix-id, svix-timestamp, and svix-signature set
func verifyWebhookSignature(payload []byte, headers http.Header, secret string) bool {
wh, err := svix.NewWebhook(secret)
if err != nil {
return false
}
err = wh.Verify(payload, headers)
return err == nil
}
Using an official verification library (pip install svix):
from svix.webhooks import Webhook
def verify_webhook_signature(payload, headers, secret):
wh = Webhook(secret)
try:
wh.verify(payload, headers) # headers: {'svix-id': ..., 'svix-timestamp': ..., 'svix-signature': ...}
return True
except Exception:
return False
Using an official verification library (dotnet add package Svix):
using Svix;
using System.Net;
// headers must have svix-id, svix-timestamp, and svix-signature set
bool VerifyWebhookSignature(string payload, WebHeaderCollection headers, string secret)
{
var wh = new Webhook(secret);
try
{
wh.Verify(payload, headers);
return true;
}
catch (Exception)
{
return false;
}
}
Once you've implemented verification, see Delivery & Retries for how to send yourself an example event to test it.