Verification SDKs
When working with webhooks, it’s crucial to ensure that the payloads you’re receiving are genuinely from Straddle and haven’t been tampered with. Payload verification helps you verify the authenticity of incoming requests by using security headers and signature checks, making your integration more secure against malicious actors. In this section, you’ll learn how to verify Straddle payloads step-by-step. By following this guide, you can confidently establish that your server is interacting with trusted data, allowing you to safely process events without worrying about spoofed or modified payloads. Whether you’re new to webhooks or just need a refresher, the following instructions will provide everything you need to get started with secure webhook verification using Straddle.Straddle has teamed up with Svix to make webhook verification as secure and straightforward as possible. This partnership means you get the best in payload authenticity and security.
Verify with SVIX Libraries
To integrate with Straddle webhooks securely, you’ll need to install the Svix libraries for your language or framework of choice. Below are installation snippets for various languages and environments.npm install svix
# Or
yarn add svix
pip install svix
# In Cargo.toml
[dependencies]
http = "1.0.0"
svix = "1.20.0"
// Gradle: Add this dependency to your project's build file:
implementation "com.svix.kotlin:svix-kotlin:0.x.y"
//Maven: Add this dependency to your project's POM:
<dependency>
<groupId>com.svix.kotlin</groupId>
<artifactId>svix-kotlin</artifactId>
<version>0.x.y</version>
</dependency>
gem install svix
dotnet add package Svix
composer require svix/svix
// Gradle: Add this dependency to your project's build file:
implementation "com.svix:svix:0.x.y"
// Maven: Add this dependency to your project's POM:
<dependency>
<groupId>com.svix</groupId>
<artifactId>svix</artifactId>
<version>0.x.y</version>
</dependency>
# On macOS install via Homebrew:
brew install svix/svix/svix
# On Windows install via Scoop:
scoop bucket add svix https://github.com/svix/scoop-svix.git
scoop install svix
# For other platforms, such as linux, checkout the CLI docs on Github.
Use the Raw Request Body
You need to use the raw request body when verifying webhooks, as the cryptographic signature is sensitive to even the slightest changes. Avoid parsing the request and then re-stringifying it before verification.
Verifying Payloads
Below is example verification code for various languages using Svix SDKs. Each code snippet shows how to verify the raw request body using the provided headers and secret.import { Webhook } from "svix";
const secret = "whsec_MfKQ9r8GKYqrTwjUPD8ILPZIo2LaLaSw";
// These were all sent from the server
const headers = {
"svix-id": "msg_p5jXN8AQM9LWM0D4loKWxJek",
"svix-timestamp": "1614265330",
"svix-signature": "v1,g0hM9SsE+OTPJTGt/tmIKtSyZlE3uFJELVlNIOLJ1OE=",
};
const payload = '{"test": 2432232314}';
const wh = new Webhook(secret);
// Throws on error, returns the verified content on success
const verifiedPayload = wh.verify(payload, headers);
from svix.webhooks import Webhook
secret = "whsec_MfKQ9r8GKYqrTwjUPD8ILPZIo2LaLaSw"
headers = {
"webhook-id": "msg_p5jXN8AQM9LWM0D4loKWxJek",
"webhook-timestamp": "1614265330",
"webhook-signature": "v1,g0hM9SsE+OTPJTGt/tmIKtSyZlE3uFJELVlNIOLJ1OE=",
}
payload = '{"test": 2432232314}'
wh = Webhook(secret)
payload = wh.verify(payload, headers)
use svix::webhooks::Webhook;
let secret = "whsec_MfKQ9r8GKYqrTwjUPD8ILPZIo2LaLaSw".to_string();
let mut headers = http::HeaderMap::new();
headers.insert("webhook-id", "msg_p5jXN8AQM9LWM0D4loKWxJek");
headers.insert("webhook-timestamp", "1614265330");
headers.insert("webhook-signature", "v1,g0hM9SsE+OTPJTGt/tmIKtSyZlE3uFJELVlNIOLJ1OE=");
let payload = b"{\"test\": 2432232314}";
let wh = Webhook::new(&secret).unwrap();
wh.verify(&payload[..], &headers).unwrap();
// returns Ok on success, Err otherwise
import (
"net/http"
svix "github.com/svix/svix-webhooks/go"
)
func verify() error {
secret := "whsec_MfKQ9r8GKYqrTwjUPD8ILPZIo2LaLaSw"
headers := http.Header{}
headers.Set("webhook-id", "msg_p5jXN8AQM9LWM0D4loKWxJek")
headers.Set("webhook-timestamp", "1614265330")
headers.Set("webhook-signature", "v1,g0hM9SsE+OTPJTGt/tmIKtSyZlE3uFJELVlNIOLJ1OE=")
payload := []byte(`{"test": 2432232314}`)
wh, err := svix.NewWebhook(secret)
if err != nil {
return err
}
return wh.Verify(payload, headers)
}
import com.svix.kotlin.Webhook
import java.net.http.HttpHeaders
val secret = "whsec_MfKQ9r8GKYqrTwjUPD8ILPZIo2LaLaSw"
val headersMap = mapOf(
"webhook-id" to listOf("msg_p5jXN8AQM9LWM0D4loKWxJek"),
"webhook-timestamp" to listOf("1614265330"),
"webhook-signature" to listOf("v1,g0hM9SsE+OTPJTGt/tmIKtSyZlE3uFJELVlNIOLJ1OE=")
)
val headers = HttpHeaders.of(headersMap) { _, _ -> true }
val payload = "{\"test\": 2432232314}"
val webhook = Webhook(secret)
webhook.verify(payload, headers)
// throws WebhookVerificationError exception on failure.
require 'svix'
headers = {
"webhook-id" => "msg_p5jXN8AQM9LWM0D4loKWxJek",
"webhook-timestamp" => "1614265330",
"webhook-signature" => "v1,g0hM9SsE+OTPJTGt/tmIKtSyZlE3uFJELVlNIOLJ1OE="
}
payload = '{"test": 2432232314}'
wh = Svix::Webhook.new("whsec_MfKQ9r8GKYqrTwjUPD8ILPZIo2LaLaSw")
json = wh.verify(payload, headers)
using Svix;
using System.Net;
var secret = "whsec_MfKQ9r8GKYqrTwjUPD8ILPZIo2LaLaSw";
var headers = new WebHeaderCollection();
headers.Set("webhook-id", "msg_p5jXN8AQM9LWM0D4loKWxJek");
headers.Set("webhook-timestamp", "1614265330");
headers.Set("webhook-signature", "v1,g0hM9SsE+OTPJTGt/tmIKtSyZlE3uFJELVlNIOLJ1OE=");
var payload = "{\"test\": 2432232314}";
var wh = new Webhook(secret);
wh.Verify(payload, headers);
// Throws on error
<?php
require_once('vendor/autoload.php');
$payload = '{"test": 2432232314}';
$header = array(
'webhook-id' => 'msg_p5jXN8AQM9LWM0D4loKWxJek',
'webhook-timestamp' => '1614265330',
'webhook-signature' => 'v1,g0hM9SsE+OTPJTGt/tmIKtSyZlE3uFJELVlNIOLJ1OE='
);
$wh = new \Svix\Webhook('whsec_MfKQ9r8GKYqrTwjUPD8ILPZIo2LaLaSw');
$json = $wh->verify($payload, $header);
// Throws on error, returns verified content on success
import com.svix.Webhook;
import java.net.http.HttpHeaders;
import java.util.*;
String secret = "whsec_MfKQ9r8GKYqrTwjUPD8ILPZIo2LaLaSw";
HashMap<String, List<String>> headerMap = new HashMap<>();
headerMap.put("webhook-id", Arrays.asList("msg_p5jXN8AQM9LWM0D4loKWxJek"));
headerMap.put("webhook-timestamp", Arrays.asList("1614265330"));
headerMap.put("webhook-signature", Arrays.asList("v1,g0hM9SsE+OTPJTGt/tmIKtSyZlE3uFJELVlNIOLJ1OE="));
HttpHeaders headers = HttpHeaders.of(headerMap, (k,v)->true);
String payload = "{\"test\": 2432232314}";
Webhook webhook = new Webhook(secret);
webhook.verify(payload, headers);
// throws WebhookVerificationError exception on failure.
export SVIX_AUTH_TOKEN="AUTH_TOKEN"
svix verify --secret whsec_MfKQ9r8GKYqrTwjUPD8ILPZIo2LaLaSw \
--msg-id msg_p5jXN8AQM9LWM0D4loKWxJek \
--timestamp 1614265330 \
--signature v1,g0hM9SsE+OTPJTGt/tmIKtSyZlE3uFJELVlNIOLJ1OE= '{"test": 2432232314}'
Framework Specific Examples
Below are examples for how to handle raw request bodies and verification in various frameworks:Python (Django)
from django.views.decorators.csrf import csrf_exempt
from django.http import HttpResponse
from svix.webhooks import Webhook, WebhookVerificationError
secret = "whsec_MfKQ9r8GKYqrTwjUPD8ILPZIo2LaLaSw"
@csrf_exempt
def webhook_handler(request):
headers = request.headers
payload = request.body
try:
wh = Webhook(secret)
msg = wh.verify(payload, headers)
except WebhookVerificationError:
return HttpResponse(status=400)
# Do something with the message...
return HttpResponse(status=204)
Python (Flask)
from flask import request, Flask
from svix.webhooks import Webhook, WebhookVerificationError
app = Flask(__name__)
secret = "whsec_MfKQ9r8GKYqrTwjUPD8ILPZIo2LaLaSw"
@app.route('/webhook/', methods=['POST'])
def webhook_handler():
headers = request.headers
payload = request.get_data()
try:
wh = Webhook(secret)
msg = wh.verify(payload, headers)
except WebhookVerificationError:
return ('', 400)
# Do something with the message...
return ('', 204)
Python (FastAPI)
from fastapi import FastAPI, Request, Response, status
from svix.webhooks import Webhook, WebhookVerificationError
app = FastAPI()
secret = "whsec_MfKQ9r8GKYqrTwjUPD8ILPZIo2LaLaSw"
@app.post("/webhook/", status_code=status.HTTP_204_NO_CONTENT)
async def webhook_handler(request: Request, response: Response):
headers = request.headers
payload = await request.body()
try:
wh = Webhook(secret)
msg = wh.verify(payload, headers)
except WebhookVerificationError:
response.status_code = status.HTTP_400_BAD_REQUEST
return
# Do something with the message...
Node.js (Next.js)
import { Webhook } from "svix";
import { buffer } from "micro";
export const config = {
api: {
bodyParser: false,
},
};
const secret = "whsec_MfKQ9r8GKYqrTwjUPD8ILPZIo2LaLaSw";
export default async function handler(req, res) {
const payload = (await buffer(req)).toString();
const headers = req.headers;
const wh = new Webhook(secret);
let msg;
try {
msg = wh.verify(payload, headers);
} catch (err) {
return res.status(400).json({});
}
// Do something with the message...
res.json({});
}
Node.js (Next.js 13 App Router)
import { Webhook } from "svix";
const webhookSecret: string = process.env.WEBHOOK_SECRET || "whsec_MfKQ9r8GKYqrTwjUPD8ILPZIo2LaLaSw";
export async function POST(req: Request) {
const webhook_id = req.headers.get("webhook-id") ?? "";
const webhook_timestamp = req.headers.get("webhook-timestamp") ?? "";
const webhook_signature = req.headers.get("webhook-signature") ?? "";
const body = await req.text();
const svx = new Webhook(webhookSecret);
let msg;
try {
msg = svx.verify(body, {
"webhook-id": webhook_id,
"webhook-timestamp": webhook_timestamp,
"webhook-signature": webhook_signature,
});
} catch (err) {
return new Response("Bad Request", { status: 400 });
}
console.log(msg);
// Rest
return new Response("OK", { status: 200 });
}
Node.js (Netlify Functions)
import { Webhook } from "svix";
const secret = "whsec_MfKQ9r8GKYqrTwjUPD8ILPZIo2LaLaSw";
export const handler = async ({body, headers}) => {
const payload = body;
const wh = new Webhook(secret);
let msg;
try {
msg = wh.verify(payload, headers);
} catch (err) {
return {
statusCode: 400,
body: JSON.stringify({})
};
}
// Do something with the message...
return {
statusCode: 200,
body: JSON.stringify({})
};
};
Node.js (Express)
import { Webhook } from "svix";
import bodyParser from "body-parser";
import express from "express";
const app = express();
const secret = "whsec_MfKQ9r8GKYqrTwjUPD8ILPZIo2LaLaSw";
app.post('/webhook', bodyParser.raw({ type: 'application/json' }), (req, res) => {
const payload = req.body;
const headers = req.headers;
const wh = new Webhook(secret);
try {
wh.verify(payload, headers);
} catch (err) {
return res.status(400).json({});
}
// Do something with the message...
res.json({});
});
app.listen(3000, () => console.log('Server started on port 3000'));
Node.js (NestJS)
main.ts:import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule, { rawBody: true });
await app.listen(3000);
}
bootstrap();
import { Controller, Post, RawBodyRequest, Req } from '@nestjs/common';
import { Request } from 'express';
import { Webhook } from 'svix';
@Controller('webhook')
class WebhookController {
@Post()
webhook(@Req() request: RawBodyRequest<Request>) {
const secret = 'whsec_MfKQ9r8GKYqrTwjUPD8ILPZIo2LaLaSw';
const wh = new Webhook(secret);
const payload = request.rawBody.toString('utf8');
const headers = request.headers;
try {
wh.verify(payload, headers);
// Do something with the message...
} catch (err) {
// handle error
}
}
}
Go (Standard lib)
package main
import (
"io"
"log"
"net/http"
svix "github.com/svix/svix-webhooks/go"
)
const secret = "whsec_MfKQ9r8GKYqrTwjUPD8ILPZIo2LaLaSw"
func main() {
wh, err := svix.NewWebhook(secret)
if err != nil {
log.Fatal(err)
}
http.HandleFunc("/webhook", func(w http.ResponseWriter, r *http.Request) {
headers := r.Header
payload, err := io.ReadAll(r.Body)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
return
}
err = wh.Verify(payload, headers)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
return
}
// Do something with the message...
w.WriteHeader(http.StatusNoContent)
})
http.ListenAndServe(":8080", nil)
}
Go (Gin)
package main
import (
"io"
"log"
"net/http"
"github.com/gin-gonic/gin"
svix "github.com/svix/svix-webhooks/go"
)
const secret = "whsec_MfKQ9r8GKYqrTwjUPD8ILPZIo2LaLaSw"
func main() {
wh, err := svix.NewWebhook(secret)
if err != nil {
log.Fatal(err)
}
r := gin.Default()
r.POST("/webhook", func(c *gin.Context) {
headers := c.Request.Header
payload, err := io.ReadAll(c.Request.Body)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
err = wh.Verify(payload, headers)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
// Do something with the message...
c.JSON(200, gin.H{})
})
r.Run()
}
Rust (axum)
use axum::{body::Bytes, http::StatusCode};
use hyper::HeaderMap;
use svix::webhooks::Webhook;
pub const SECRET: &'static str = "whsec_MfKQ9r8GKYqrTwjUPD8ILPZIo2LaLaSw";
pub async fn webhook_in(headers: HeaderMap, body: Bytes) -> StatusCode {
let wh = Webhook::new(SECRET);
if wh.is_err() {
return StatusCode::INTERNAL_SERVER_ERROR;
}
if wh.unwrap().verify(&body[..], &headers).is_err() {
return StatusCode::BAD_REQUEST;
}
// Do something with the message...
StatusCode::NO_CONTENT
}
Ruby (Ruby on Rails)
config/routes.rb:Rails.application.routes.draw do
post "/webhook", to: "webhook#index"
end
require 'svix'
class WebhookController < ApplicationController
protect_from_forgery with: :null_session
def index
begin
payload = request.body.read
headers = request.headers
wh = Svix::Webhook.new("whsec_MfKQ9r8GKYqrTwjUPD8ILPZIo2LaLaSw")
json = wh.verify(payload, headers)
# Do something with the message...
head :no_content
rescue
head :bad_request
end
end
end
PHP (Laravel)
use Illuminate\Http\Request;
use Svix\Webhook;
use Svix\Exception\WebhookVerificationException;
Route::post('webhook', function(Request $request) {
$payload = $request->getContent();
$headers = collect($request->headers->all())->map(fn($item) => $item[0]);
try {
$wh = new Webhook("whsec_MfKQ9r8GKYqrTwjUPD8ILPZIo2LaLaSw");
$json = $wh->verify($payload, $headers->toArray());
// Do something with the message...
return response()->noContent();
} catch (WebhookVerificationException $e) {
return response(null, 400);
}
});