Testing Webhooks Locally

Test receiving and verifying Bot status event webhooks locally



Before you can receive bot status change events locally, make sure you've set up Local Webhook Development.


Configure your endpoint

  1. Go to the Recall dashboard and click Add Endpoint

  1. In Endpoint URL, enter your static ngrok URL along with the path to your webhook handler.

Configure your local server

Copy the signing secret for your new webhook endpoint

Configure your server to use this secret locally.


Use an environment variable

Below the secret is hard-coded into our handler for demonstration purposes, but we recommend using an environment variable.

This will allow you to separate this sensitive value from version control and make configuring secrets for different environments much easier.

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

const secret = "whsec_yhVJ7lfTGMQDJY8YSq9aGcsw4I7/XJIz";

app.post('/webhook/recall', 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) {

    // Do something with the message...

from fastapi import Request, Response, status

from svix.webhooks import Webhook, WebhookVerificationError

secret = "whsec_yhVJ7lfTGMQDJY8YSq9aGcsw4I7/XJIz"

@router.post("/webhook/recall", status_code=status.HTTP_204_NO_CONTENT)
async def webhook_handler(request: Request, response: Response):
    headers = request.headers
    payload = await request.body()

        wh = Webhook(secret)
        msg = wh.verify(payload, headers)
    except WebhookVerificationError as e:
        response.status_code = status.HTTP_400_BAD_REQUEST

    # Do someting with the message...
package main

import (

	svix "github.com/svix/svix-webhooks/go"

// Configure your secret from above.
// Alternatively, use a local config file.
const secret = "whsec_yhVJ7lfTGMQDJY8YSq9aGcsw4I7/XJIz"

func main() {
	wh, err := svix.NewWebhook(secret)
	if err != nil {

	http.HandleFunc("/webhook/recall", func(w http.ResponseWriter, r *http.Request) {
		headers := r.Header
		payload, err := io.ReadAll(r.Body)
		if err != nil {
			fmt.Println("error:", err)

		err = wh.Verify(payload, headers)
		if err != nil {
			fmt.Println("error:", err)

		fmt.Printf("verified payload: %s", string(payload))

		// Do something with the message...


	fmt.Println("Starting server on port 8080")
	http.ListenAndServe(":8080", nil)

Receive your first webhook events

Start your server and tunnel

Start up your server and open an ngrok tunnel for your static domain, pointing it to the port of your local server.

Example: Start a node.js server on port 3000 and expose it publicly at my-static-domain.ngrok-free.app

# Start the server
npm run start --port 3000

# In a new terminal: Start the ngrok tunnel
ngrok http --domain my-static-domain.ngrok-free.app 3000

Create a bot

  1. Start an instant Google Meet call: https://meet.google.com/
  2. Call Create Bot, setting meeting_url to the URL of the meeting you just created.
  3. As the bot joins the call and starts recording, you should see your server receive the webhook events and verify their signatures successfully.
received event: {"data":{"bot_id":"92e24581-f82b-401a-8f75-88e64b04c24e","status":{"code":"joining_call","created_at":"2024-02-17T16:44:00.505440+00:00","message":null,"sub_code":null}},"event":"bot.status_change"}
received event: {"data":{"bot_id":"92e24581-f82b-401a-8f75-88e64b04c24e","status":{"code":"in_waiting_room","created_at":"2024-02-17T16:44:17.692342+00:00","message":null,"sub_code":null}},"event":"bot.status_change"}
received event: {"data":{"bot_id":"92e24581-f82b-401a-8f75-88e64b04c24e","status":{"code":"in_call_not_recording","created_at":"2024-02-17T16:44:23.288984+00:00","message":null,"sub_code":null}},"event":"bot.status_change"}
received event: {"data":{"bot_id":"92e24581-f82b-401a-8f75-88e64b04c24e","status":{"code":"in_call_recording","created_at":"2024-02-17T16:44:23.295099+00:00","message":null,"recording_id":"1ec9d5a7-5404-4f3e-a40b-961324db4b20","sub_code":null}},"event":"bot.status_change"}