Skip to main content

Webhooks

Webhooks can be configured in the Gabber dashboard.

For a list of webhook payloads, see Webhook.

Example Use Cases

  • An app that has a credit sytem could use the usage.tracked webhook to deduct credits on the platform.
  • Realtime sessions that use tools calls could use the tool.calls_finished webhook to respond into the session with tool call results.
  • The realtime_session.state_changed webhook can be used to make relevant updates when a phone number is called

Local Development

Since your computer is likely not exposed on a static public IP address to the internet, Gabber can't send webhook requests to it. This presents a challenge for developing webhook handlers.

The easiest way to get around this is to use a tunneling service. Ngrok is a very popular, free choice.

Running:

ngrok http 3000

gives you a public endpoint that proxies all traffic to your localhost:3000. Learn more at ngrok.com.

Deliverability

Webhook requests are retried 3 times to ensure deliverability.

Security

Since your backend's webhook endpoints must be public to recieve webhooks, it's important to verify that Gabber is the sender (and not a malicious actor).

Every webhook request is sent with an X-Webhook-Signature header which is an HMAC-SHA256 hex string of the request body signed with your configured Gabber API key.

The following shows an example taken from our Rizz.AI Example NextJS App of how to verify webhook signatures:

import { NextRequest } from "next/server";
import crypto from "crypto";
import { CreditsController } from "@/lib/server/controller/credits";
import { UserController } from "@/lib/server/controller/user";

export async function POST(req: NextRequest) {
const textBody = await req.text();
const webhookSignature = req.headers.get("X-Webhook-Signature");
const key = process.env.GABBER_API_KEY;

if (!webhookSignature) {
return new Response("No signature provided", { status: 400 });
}

if (!key) {
return new Response("Server misconfigured - missing API key");
}

const computedSignature = crypto
.createHmac("sha256", key)
.update(textBody, "utf8")
.digest("hex");

if (computedSignature !== webhookSignature) {
// This could be from the wrong api key configured here or in the webhook gabber dashboard
// or it could be from a malicious actor trying to send a request
console.error("Signature mismatch");
return new Response("Invalid signature", { status: 403 });
}

const parsedBody = JSON.parse(textBody);
const { type, payload } = parsedBody;
if (type === "usage.tracked") {
const { human_id, type, value } = payload;
if (type === "conversational_seconds") {
// Use 1 credit per second
const creditCost = value;
const resp = await CreditsController.reportCreditUsage(
human_id,
creditCost * -1,
);
const entry = resp.data;
await UserController.updateLimits(human_id, entry.balance);
}
}
return new Response(null, { status: 200 });
}