Webhooks

Receive real-time delivery notifications for voice notes via webhook events.

Setting up your webhook URL

Configure your webhook endpoint in the Svara dashboard under Settings > Webhooks. Svara sends POST requests to this URL whenever a voice note status changes.

Your endpoint must:

  • Accept POST requests with a JSON body
  • Return a 200 status code within 10 seconds
  • Be publicly accessible via HTTPS

If your endpoint returns a non-2xx status code or times out, Svara retries the delivery up to 5 times with exponential backoff (1s, 5s, 30s, 2min, 10min).

Event types

| Event | Description | |---|---| | voice_note.sent | The voice note was successfully delivered to the platform | | voice_note.delivered | The recipient received/opened the voice note (where supported) | | voice_note.failed | Delivery failed permanently after all retries |

Payload format

Every webhook request includes a JSON body with the event type and full message details:

{
  "event": "voice_note.sent",
  "timestamp": "2026-03-14T12:00:02Z",
  "data": {
    "id": "msg_a1b2c3d4e5f6",
    "platform": "telegram",
    "recipient": "123456789",
    "status": "sent",
    "created_at": "2026-03-14T12:00:00Z",
    "delivered_at": "2026-03-14T12:00:02Z",
    "error": null
  }
}

For failed deliveries, the error field contains details:

{
  "event": "voice_note.failed",
  "timestamp": "2026-03-14T12:01:30Z",
  "data": {
    "id": "msg_x9y8z7w6v5u4",
    "platform": "linkedin",
    "recipient": "john-doe-12345",
    "status": "failed",
    "created_at": "2026-03-14T12:00:00Z",
    "delivered_at": null,
    "error": {
      "code": "session_expired",
      "message": "The LinkedIn session cookie has expired."
    }
  }
}

Signature verification

Every webhook request includes an X-Svara-Signature header containing an HMAC-SHA256 signature of the request body. Verify this signature to ensure the webhook was sent by Svara and has not been tampered with.

Your webhook secret is available in the dashboard under Settings > Webhooks.

Verification steps

  1. Read the raw request body as a string (do not parse JSON first).
  2. Compute an HMAC-SHA256 digest of the body using your webhook secret.
  3. Compare the computed signature to the X-Svara-Signature header value.
  4. Reject the request if the signatures do not match.

Example: Node.js webhook handler

import crypto from "crypto";
import express from "express";

const app = express();
const WEBHOOK_SECRET = process.env.SVARA_WEBHOOK_SECRET;

app.post("/webhooks/svara", express.raw({ type: "application/json" }), (req, res) => {
  // Verify the signature
  const signature = req.headers["x-svara-signature"];
  const expectedSignature = crypto
    .createHmac("sha256", WEBHOOK_SECRET)
    .update(req.body)
    .digest("hex");

  if (signature !== expectedSignature) {
    console.error("Invalid webhook signature");
    return res.status(401).send("Invalid signature");
  }

  // Parse the event
  const event = JSON.parse(req.body);

  switch (event.event) {
    case "voice_note.sent":
      console.log(`Voice note ${event.data.id} delivered to ${event.data.platform}`);
      break;
    case "voice_note.delivered":
      console.log(`Voice note ${event.data.id} opened by recipient`);
      break;
    case "voice_note.failed":
      console.error(`Voice note ${event.data.id} failed: ${event.data.error.message}`);
      // Retry, alert, or log the failure
      break;
  }

  res.status(200).send("OK");
});

app.listen(3000);

Example: Python webhook handler

import hmac
import hashlib
import json
from flask import Flask, request

app = Flask(__name__)
WEBHOOK_SECRET = "your_webhook_secret"

@app.route("/webhooks/svara", methods=["POST"])
def handle_webhook():
    # Verify the signature
    signature = request.headers.get("X-Svara-Signature")
    expected = hmac.new(
        WEBHOOK_SECRET.encode(),
        request.data,
        hashlib.sha256,
    ).hexdigest()

    if not hmac.compare_digest(signature, expected):
        return "Invalid signature", 401

    event = request.get_json()

    if event["event"] == "voice_note.sent":
        print(f"Delivered: {event['data']['id']}")
    elif event["event"] == "voice_note.failed":
        print(f"Failed: {event['data']['id']} - {event['data']['error']['message']}")

    return "OK", 200

Testing webhooks locally

During development, use a tool like ngrok to expose your local server:

ngrok http 3000

Copy the HTTPS URL from ngrok and set it as your webhook URL in the Svara dashboard. You can also use sk_test_ keys to trigger simulated webhook events without sending real voice notes.

Ask Svara

Hey! I'm the Svara assistant. Ask me anything about integrating voice notes into your product.

Powered by Svara