> ## Documentation Index
> Fetch the complete documentation index at: https://docs.straddle.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Verify webhook signatures with SDKs

> Securely verify Straddle webhook signatures and payloads using Svix SDKs in Node.js, Python, Ruby, Go, and other languages with code examples.

# 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.

<Info>
  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.
</Info>

## 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.

<CodeGroup>
  ```bash javascript theme={null}
  npm install svix
  # Or
  yarn add svix
  ```

  ```bash python theme={null}
  pip install svix
  ```

  ```toml rust theme={null}
  # In Cargo.toml
  [dependencies]
  http = "1.0.0"
  svix = "1.20.0"
  ```

  ```groovy kotlin theme={null}
  // 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>
  ```

  ```bash ruby theme={null}
  gem install svix
  ```

  ```bash csharp theme={null}
  dotnet add package Svix
  ```

  ```bash php theme={null}
  composer require svix/svix
  ```

  ```groovy java theme={null}
  // 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>
  ```

  ```bash cli theme={null}
  # 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.
  ```
</CodeGroup>

## Use the Raw Request Body

<Tip>
  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.
</Tip>

The signature for each endpoint is available where you added the endpoint, e.g., the Straddle dashboard.

## 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.

<CodeGroup>
  ```javascript javascript theme={null}
  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);
  ```

  ```python python theme={null}
  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)
  ```

  ```rust rust theme={null}
  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
  ```

  ```go go theme={null}
  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)
  }
  ```

  ```kotlin kotlin theme={null}
  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.
  ```

  ```ruby ruby theme={null}
  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)
  ```

  ```csharp csharp theme={null}
  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 php theme={null}
  <?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
  ```

  ```java java theme={null}
  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.
  ```

  ```bash cli theme={null}
  export SVIX_AUTH_TOKEN="AUTH_TOKEN"
  svix verify --secret whsec_MfKQ9r8GKYqrTwjUPD8ILPZIo2LaLaSw \
  --msg-id msg_p5jXN8AQM9LWM0D4loKWxJek \
  --timestamp 1614265330 \
  --signature v1,g0hM9SsE+OTPJTGt/tmIKtSyZlE3uFJELVlNIOLJ1OE= '{"test": 2432232314}'
  ```
</CodeGroup>

## Framework Specific Examples

Below are examples for how to handle raw request bodies and verification in various frameworks:

### Python (Django)

```python theme={null}
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)

```python theme={null}
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)

```python theme={null}
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)

```javascript theme={null}
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)

```typescript theme={null}
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)

```typescript theme={null}
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)

```typescript theme={null}
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:**

```typescript theme={null}
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();
```

**webhook.controller.ts:**

```typescript theme={null}
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)

```go theme={null}
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 theme={null}
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)

```rust theme={null}
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:**

```ruby theme={null}
Rails.application.routes.draw do
  post "/webhook", to: "webhook#index"
end
```

**app/controllers/webhook\_controller.rb:**

```ruby theme={null}
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)

```php theme={null}
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);
    }
});
```

```
```
