---
title: How to Verify Webhooks with the Svix Libraries
---
# How to Verify Webhooks with the Svix Libraries

As shown in the [Why Verify section](./why.mdx), verifying operational webhooks is very important. This section describes how to do it.

<ReceivingSkillCallout />

## Verifying using our official libraries

First install the libraries if you haven't already:

<CodeTabs items={["JavaScript","Python","Rust","Go","Java","Kotlin","Ruby","C#","PHP","CLI","cURL"]}>
<TabItem value="JavaScript">

```js
npm install svix
// Or
yarn add svix
```

</TabItem>
<TabItem value="Python">

```sh
pip install svix
```

</TabItem>
<TabItem value="Rust">

```toml
http = "1.0.0"
svix = "1.20.0"
```

</TabItem>
<TabItem value="Go">

```shell
go get github.com/svix/svix-webhooks/go
```

</TabItem>
<TabItem value="Java">

Gradle: Add this dependency to your project's build file:

```groovy
implementation "com.svix:svix:0.x.y"
```

Maven: Add this dependency to your project's POM:

```xml
<dependency>
  <groupId>com.svix</groupId>
  <artifactId>svix</artifactId>
  <version>0.x.y</version>
</dependency>
```

</TabItem>
<TabItem value="Kotlin">

Gradle: Add this dependency to your project's build file:

```groovy
implementation "com.svix.kotlin:svix-kotlin:0.x.y"
```

Maven: Add this dependency to your project's POM:

```xml
<dependency>
  <groupId>com.svix.kotlin</groupId>
  <artifactId>svix-kotlin</artifactId>
  <version>0.x.y</version>
</dependency>
```

</TabItem>
<TabItem value="Ruby">

```shell
gem install svix
```

</TabItem>
<TabItem value="C#">

```shell
dotnet add package Svix
```

</TabItem>
<TabItem value="PHP">

```shell
composer require svix/svix
```

</TabItem>
<TabItem value="CLI">

On macOS install via <a href="https://brew.sh/">Homebrew</a>:
```sh
brew install svix/svix/svix-cli
```

On Windows install via <a href="https://scoop.sh/">Scoop</a>:
```sh
scoop bucket add svix https://github.com/svix/scoop-svix.git
scoop install svix
```

For other platforms, such as linux, checkout the <a href="https://github.com/svix/svix-cli#installation">CLI docs</a> on Github.

</TabItem>
<TabItem value="cURL">

```shell
# Install cURL. E.g. on arch linux:
pacman -S curl
```

</TabItem>
</CodeTabs>

Then verify webhooks using the code below. The payload is the raw (string) body of the request, and the headers are the headers passed in the request.

<Callout type="important">

**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.
You should watch out for frameworks that parse the request as JSON and then stringify it because this too will break the signature verification.

See examples below for how to get the raw request body with different frameworks.

</Callout>
The signature you should get from where you added the endpoint, e.g. the application portal.

<CodeTabs items={["JavaScript","Python","Rust","Go","Java","Kotlin","Ruby","C#","PHP","CLI","cURL"]}>
<TabItem value="JavaScript">

```js
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
wh.verify(payload, headers);
```

</TabItem>
<TabItem value="Python">

```python
from svix.webhooks import Webhook

secret = "whsec_MfKQ9r8GKYqrTwjUPD8ILPZIo2LaLaSw"

# These were all sent from the server
headers = {
  "svix-id": "msg_p5jXN8AQM9LWM0D4loKWxJek",
  "svix-timestamp": "1614265330",
  "svix-signature": "v1,g0hM9SsE+OTPJTGt/tmIKtSyZlE3uFJELVlNIOLJ1OE=",
}
payload = '{"test": 2432232314}'

wh = Webhook(secret)
# Throws on error, returns the verified content on success
wh.verify(payload, headers)
```

</TabItem>
<TabItem value="Rust">

```rust
use svix::webhooks::Webhook;

let secret = "whsec_MfKQ9r8GKYqrTwjUPD8ILPZIo2LaLaSw".to_string();

let mut headers = http::HeaderMap::new();
headers.insert("svix-id", "msg_p5jXN8AQM9LWM0D4loKWxJek");
headers.insert("svix-timestamp", "1614265330");
headers.insert(
    "svix-signature",
    "v1,g0hM9SsE+OTPJTGt/tmIKtSyZlE3uFJELVlNIOLJ1OE=",
);

let payload = b"{\"test\": 2432232314}";

let wh = Webhook::new(&secret)?;
wh.verify(&payload, &headers)?;
// returns Ok on success, Err otherwise
```

</TabItem>
<TabItem value="Go">

```go
import (
	svix "github.com/svix/svix-webhooks/go"
)

secret := "whsec_MfKQ9r8GKYqrTwjUPD8ILPZIo2LaLaSw"

// These were all sent from the server
headers := http.Header{}
headers.Set("svix-id", "msg_p5jXN8AQM9LWM0D4loKWxJek")
headers.Set("svix-timestamp", "1614265330")
headers.Set("svix-signature", "v1,g0hM9SsE+OTPJTGt/tmIKtSyZlE3uFJELVlNIOLJ1OE=")

payload := []byte(`{"test": 2432232314}`)

wh, err := svix.NewWebhook(secret)
err := wh.Verify(payload, headers)
// returns nil on success, error otherwise
```

</TabItem>
<TabItem value="Java">

```java
import com.svix.Webhook;

String secret = "whsec_MfKQ9r8GKYqrTwjUPD8ILPZIo2LaLaSw";

// These were all sent from the server
HashMap<String, List<String>> headerMap = new HashMap<String, List<String>>();
headerMap.put("svix-id", Arrays.asList("msg_p5jXN8AQM9LWM0D4loKWxJek"));
headerMap.put("svix-timestamp", Arrays.asList("1614265330"));
headerMap.put("svix-signature", Arrays.asList("v1,g0hM9SsE+OTPJTGt/tmIKtSyZlE3uFJELVlNIOLJ1OE="));
HttpHeaders headers = HttpHeaders.of(headerMap, BiPredicate<String, String>)

String payload = "{\"test\": 2432232314}";

Webhook webhook = new Webhook(secret);

webhook.verify(payload, headers)
// throws WebhookVerificationError exception on failure.
```

</TabItem>
<TabItem value="Kotlin">

```kotlin
import com.svix.kotlin.Webhook

val secret = "whsec_MfKQ9r8GKYqrTwjUPD8ILPZIo2LaLaSw";

// These were all sent from the server
val headersMap = mapOf(
    "svix-id" to listOf("msg_p5jXN8AQM9LWM0D4loKWxJek"),
    "svix-timestamp" to listOf("1614265330"),
    "svix-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.
```

</TabItem>
<TabItem value="Ruby">

```ruby
require 'svix'


# These were all sent from the server
headers = {
  "svix-id" => "msg_p5jXN8AQM9LWM0D4loKWxJek",
  "svix-timestamp" => "1614265330",
  "svix-signature" => "v1,g0hM9SsE+OTPJTGt/tmIKtSyZlE3uFJELVlNIOLJ1OE="
}
payload = '{"test": 2432232314}'

wh = Svix::Webhook.new("whsec_MfKQ9r8GKYqrTwjUPD8ILPZIo2LaLaSw")
# Raises on error, returns the verified content on success
json = wh.verify(payload, headers)
```

</TabItem>
<TabItem value="C#">

```csharp
using Svix;
using System.Net;

// These were all sent from the server
var headers = new WebHeaderCollection();
headers.Set("svix-id", "msg_p5jXN8AQM9LWM0D4loKWxJek");
headers.Set("svix-timestamp", "1614265330");
headers.Set("svix-signature", "v1,g0hM9SsE+OTPJTGt/tmIKtSyZlE3uFJELVlNIOLJ1OE=");
var payload = "{\"test\": 2432232314}";

var wh = new Webhook("whsec_MfKQ9r8GKYqrTwjUPD8ILPZIo2LaLaSw/Je4ZJEGP1QFb");

// Throws on error
wh.Verify(payload, headers);
```

</TabItem>
<TabItem value="PHP">

```php
// import using composers autoload
require_once('vendor/autoload.php');
// or manually
require_once('/path/to/svix/php/init.php');

// These were all sent from the server
$payload = '{"test": 2432232314}';
$header = array(
        'svix-id'  => 'msg_p5jXN8AQM9LWM0D4loKWxJek',
        'svix-timestamp' => '1614265330',
        'svix-signature' => 'v1,g0hM9SsE+OTPJTGt/tmIKtSyZlE3uFJELVlNIOLJ1OE=',
    );

// Throws on error, returns the verified content on success
$wh = new \Svix\Webhook('whsec_MfKQ9r8GKYqrTwjUPD8ILPZIo2LaLaSw');
$json = $wh->verify($payload, $header);
```

</TabItem>
<TabItem value="CLI">

```shell
export SVIX_AUTH_TOKEN="AUTH_TOKEN"
svix verify --secret whsec_MfKQ9r8GKYqrTwjUPD8ILPZIo2LaLaSw --msg-id msg_p5jXN8AQM9LWM0D4loKWxJek --timestamp 1614265330 --signature v1,g0hM9SsE+OTPJTGt/tmIKtSyZlE3uFJELVlNIOLJ1OE= '{"test": 2432232314}'
```

</TabItem>
<TabItem value="cURL">

No easy way to verify the signature just with cURL.

</TabItem>
</CodeTabs>


## Framework specific examples

Here are examples on how to adjust the above examples to your favourite framework!

### Python (Django)

```python
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 as e:
        return HttpResponse(status=400)

    # Do something with the message...

    return HttpResponse(status=204)
```

### Python (Flask)

```python
from flask import request

from svix.webhooks import Webhook, WebhookVerificationError

secret = "whsec_MfKQ9r8GKYqrTwjUPD8ILPZIo2LaLaSw"

@app.route('/webhook/')
def webhook_handler():
    headers = request.headers
    payload = request.get_data()

    try:
        wh = Webhook(secret)
        msg = wh.verify(payload, headers)
    except WebhookVerificationError as e:
        return ('', 400)

    # Do something with the message...

    return ('', 204)
```

### Python (FastAPI)

```python
from fastapi import Request, Response, status

from svix.webhooks import Webhook, WebhookVerificationError

secret = "whsec_MfKQ9r8GKYqrTwjUPD8ILPZIo2LaLaSw"

@router.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 as e:
        response.status_code = status.HTTP_400_BAD_REQUEST
        return

    # Do something with the message...
```

### Node.js (Next.js)

The [`svix-example`](https://github.com/svix/svix-example) repo contains an [example](https://github.com/svix/svix-example/blob/main/src/pages/api/consumer/webhooks.ts) of how to verify and use webhooks in a Next.js application.

```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) {
        res.status(400).json({});
    }

    // Do something with the message...

    res.json({});
}
```

### Node.js (Next.js 13 App Router)

```js
import { Webhook } from "svix";

const webhookSecret: string = process.env.WEBHOOK_SECRET || "your-secret";

export async function POST(req: Request) {
  const svix_id = req.headers.get("svix-id") ?? "";
  const svix_timestamp = req.headers.get("svix-timestamp") ?? "";
  const svix_signature = req.headers.get("svix-signature") ?? "";

  const body = await req.text();

  const sivx = new Webhook(webhookSecret);

  let msg;

  try {
    msg = sivx.verify(body, {
      "svix-id": svix_id,
      "svix-timestamp": svix_timestamp,
      "svix-signature": svix_signature,
    });
  } catch (err) {
    return new Response("Bad Request", { status: 400 });
  }

  console.log(msg);

  // Rest

  return new Response("OK", { status: 200 });
}
```

### Node.js (Netlify Functions)

```js
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) {
        res.status(400).json({});
    }

    // Do something with the message...

    res.json({});
}
```


### Node.js (Express)

**Note:** When integrating this example into a larger codebase, you will have to
make sure not to apply the `express.json()` middleware to the webhook route,
because the payload has to be passed to `wh.verify` without any prior parsing.

```js
import { Webhook } from "svix";
import bodyParser from "body-parser";

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);
    let msg;
    try {
        msg = wh.verify(payload, headers);
    } catch (err) {
        res.status(400).json({});
    }

    // Do something with the message...

    res.json({});
});
```

### Node.js (NestJS)

Initialize the application with the `rawBody` flag set to true. See the (NestJS docs)[https://docs.nestjs.com/faq/raw-body#raw-body] for details.

```js
// main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.create(
    AppModule,
    { rawBody: true } // add rawBody flag
  );
  await app.listen(3000);
}
bootstrap();

```

```js
// webhook.controller.ts
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;

    let msg;
    try {
      msg = wh.verify(payload, headers);
    } catch (err) {
      // handle error
    }

    // Do something with the message...
  }
}

```

### Node.js (Nuxt)

```js
import { Webhook } from "svix";

const secret = "whsec_MfKQ9r8GKYqrTwjUPD8ILPZIo2LaLaSw";

export default defineEventHandler(async (event) => {
  const headers = getRequestHeaders(event);
  const payload = await readRawBody(event);

  const wh = new Webhook(secret);

  let msg;

  try {
      msg = wh.verify(payload, headers);
  } catch (err) {
      setResponseStatus(event, 400);
      return "Bad Request";
  }

  // Do something with the message...
  return "OK";
})
```

### Go (Standard lib)

```go
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)

```go
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)

Add the `webhook_in` route below to an axum router.

```rust
use axum::{body::Bytes, http::StatusCode};
use hyper::HeaderMap;

pub const SECRET: &'static str = "whsec_MfKQ9r8GKYqrTwjUPD8ILPZIo2LaLaSw";

pub async fn webhook_in(headers: HeaderMap, body: Bytes) -> StatusCode {
    let Ok(wh) = svix::webhooks::Webhook::new(SECRET) else {
        return StatusCode::INTERNAL_SERVER_ERROR;
    };

    if let Err(_) = wh.verify(&body, &headers) {
        return StatusCode::BAD_REQUEST;
    }

    // Do something with the message...

    StatusCode::NO_CONTENT
}
```

### Ruby (Ruby on Rails)

Once you've set up your project add a route to your `config/routes.rb` file at the top of the `Rails.application.routes.draw` block:

```rb
Rails.application.routes.draw do
  post "/webhook", to: "webhook#index"

  # For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html
end
```

The route above declares that `POST /webhook` requests are mapped to the index action of `WebhookController`.

To create `WebhookController` and its index action, we'll run the controller generator (with the `--skip-routes` option because we already have an appropriate route):

```sh
bin/rails generate controller Webhook index --skip-routes
```

Rails will create several files for you:

```sh
    create  app/controllers/webhook_controller.rb
    invoke  erb
    create    app/views/webhook
    create    app/views/webhook/index.html.erb
    invoke  test_unit
    create    test/controllers/webhook_controller_test.rb
    invoke  helper
    create    app/helpers/webhook_helper.rb
    invoke    test_unit
    invoke  assets
    invoke    scss
    create      app/assets/stylesheets/webhook.scss
```

Now we can add our verification logic to the newly created `app/controllers/webhook_controller.rb` file:

```rb
require 'svix'

class WebhookController < ApplicationController
  protect_from_forgery with: :null_session # disables CSRF middleware; required for API endpoints

  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)

In your `routes/api.php` file add the following after the last use directive:

```php
use Svix\Webhook;
use Svix\Exception\WebhookVerificationException;

Route::post('webhook', function(Request $request) {
    $payload = $request->getContent();
    $headers = collect($request->headers->all())->transform(function ($item) {
        return $item[0];
    });

    try {
        $wh = new Webhook("whsec_MfKQ9r8GKYqrTwjUPD8ILPZIo2LaLaSw");
        $json = $wh->verify($payload, $headers);

        # Do something with the message...

        return response()->noContent();
    } catch (WebhookVerificationException $e) {
        return response(null, 400);
    }
});
```
