Skip to main content

Overview

Levers uses Svix to securely deliver webhooks. Every webhook request includes cryptographic signatures that you can verify to ensure the request originated from Levers and wasn’t tampered with.

Signature Headers

Each webhook POST request includes the following headers:
HeaderDescription
svix-idUnique identifier for this webhook delivery
svix-timestampUnix timestamp when the webhook was sent
svix-signatureComma-delimited list of signatures
The svix-signature header contains one or more signatures in the format:
svix-signature: t=timestamp,v1=signature1,v1=signature2,...

Getting Your Webhook Secret

Each webhook endpoint in Levers has its own unique secret key. To find it:
  1. Navigate to Settings > Webhooks in the Levers Dashboard
  2. Click on your configured webhook endpoint
  3. Copy the Webhook Secret (starts with whsec_)
  4. Store this securely in your application (use environment variables)
Never commit your webhook secret to version control. Always use environment variables or a secure secret management system.

Installation

Install the Svix library for your preferred language:
npm install svix

Basic Verification

import { Webhook } from "svix";

const secret = "whsec_MfKQ9r8GKYqrTwjUPD8ILPZIo2LaLaSw";

// These are sent from Levers in the request headers and body
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 event = wh.verify(payload, headers);

Framework Examples

Node.js Frameworks

import { Webhook } from "svix";
import express from 'express';

const app = express();
const secret = 'whsec_YOUR_WEBHOOK_SECRET_HERE';

// Raw body parser - IMPORTANT: do not use express.json() for webhooks
app.use('/webhook', express.raw({ type: 'application/json' }));

app.post('/webhook', (req, res) => {
  const payload = req.body;
  const headers = req.headers;

  const wh = new Webhook(secret);

  try {
    const event = wh.verify(payload.toString(), {
      'svix-id': headers['svix-id'],
      'svix-timestamp': headers['svix-timestamp'],
      'svix-signature': headers['svix-signature']
    });

    // Process the verified event
    const eventType = event.type;
    const eventData = event.data;

    if (eventType === 'phone_call.ended') {
      console.log(`Call ended: ${eventData.providerCallId}`);
      // Your business logic here
    }

    res.status(200).json({ status: 'success' });
  } catch (err) {
    console.error('Webhook verification failed:', err);
    res.status(401).json({ error: 'Invalid signature' });
  }
});

app.listen(3000, () => console.log('Webhook server running on port 3000'));

Python Frameworks

from flask import Flask, request
from svix.webhooks import Webhook, WebhookVerificationError

app = Flask(__name__)
secret = "whsec_YOUR_WEBHOOK_SECRET_HERE"

@app.route('/webhook', methods=['POST'])
def webhook_handler():
    headers = request.headers
    payload = request.get_data()  # Get raw body

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

    # Process the verified event
    event_type = event.get('type')
    event_data = event.get('data')

    if event_type == 'phone_call.ended':
        print(f"Call ended: {event_data['providerCallId']}")
        # Your business logic here

    return ('Success', 204)

if __name__ == '__main__':
    app.run(port=3000)

Go Frameworks

package main

import (
    "io"
    "log"
    "net/http"

    "github.com/gin-gonic/gin"
    svix "github.com/svix/svix-webhooks/go"
)

const secret = "whsec_YOUR_WEBHOOK_SECRET_HERE"

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": "Invalid signature"})
            return
        }

        // Process the verified event...
        c.JSON(200, gin.H{"status": "success"})
    })
    r.Run()
}

Ruby on Rails

Add a route to config/routes.rb:
Rails.application.routes.draw do
  post "/webhook", to: "webhook#index"
end
Create the controller app/controllers/webhook_controller.rb:
require 'svix'

class WebhookController < ApplicationController
  protect_from_forgery with: :null_session  # Disable CSRF for API endpoints

  def index
    begin
      payload = request.body.read
      headers = request.headers
      wh = Svix::Webhook.new("whsec_YOUR_WEBHOOK_SECRET_HERE")

      event = wh.verify(payload, headers)

      # Process the verified event
      event_type = event['type']
      event_data = event['data']

      if event_type == 'phone_call.ended'
        puts "Call ended: #{event_data['providerCallId']}"
        # Your business logic here
      end

      head :no_content
    rescue
      head :bad_request
    end
  end
end

PHP (Laravel)

In routes/api.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_YOUR_WEBHOOK_SECRET_HERE");
        $event = $wh->verify($payload, $headers);

        // Process the verified event
        $eventType = $event['type'];
        $eventData = $event['data'];

        if ($eventType === 'phone_call.ended') {
            Log::info("Call ended: " . $eventData['providerCallId']);
            // Your business logic here
        }

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

Testing Webhooks Locally

To test webhooks during development, use a tool like ngrok or localtunnel to expose your local server to the internet:
# Install ngrok
brew install ngrok  # macOS
# or visit: https://ngrok.com/download

# Start your local server
python app.py  # Your webhook server

# In another terminal, expose it
ngrok http 3000
Then use the ngrok URL (e.g., https://abc123.ngrok.io/webhook) in your webhook configuration.
Contact our support team if you encounter issues with webhook verification.