Skip to content

Webhooks

Optional

Complete the Quick start first. Webhooks are for server-side message events.

Sendsar can send signed POST requests to your webhook handler on message events.

What you need

  • A webhook endpoint on your server (publicly reachable)
  • Your webhook secret (whsec_…) stored server-side

Webhook secret is not an API key

Use the webhook secret only to verify webhook signatures. Do not send it in API headers.


Verify signatures

Sendsar sends a signature in the X-Webhook-Signature header:

  • Format: sha256=<hex>
  • Body: HMAC-SHA256 of the raw request body using your webhook secret
js
import { createHmac } from "node:crypto";

const expected =
  "sha256=" +
  createHmac("sha256", process.env.SENDSAR_WEBHOOK_SECRET)
    .update(rawBody)
    .digest("hex");

if (expected !== request.headers.get("x-webhook-signature")) {
  throw new Error("Invalid webhook signature");
}
php
$expected = 'sha256=' . hash_hmac(
    'sha256',
    $request->getContent(),
    config('sendsar.webhook_secret')
);

if (!hash_equals($expected, $request->header('X-Webhook-Signature', ''))) {
    abort(401, 'Invalid webhook signature');
}
py
import hmac
import hashlib

expected = "sha256=" + hmac.new(
    WEBHOOK_SECRET.encode(),
    raw_body,
    hashlib.sha256,
).hexdigest()

if not hmac.compare_digest(expected, request.headers.get("X-Webhook-Signature", "")):
    raise HTTPException(status_code=401, detail="Invalid signature")
go
mac := hmac.New(sha256.New, []byte(webhookSecret))
mac.Write(rawBody)
expected := "sha256=" + hex.EncodeToString(mac.Sum(nil))
if !hmac.Equal([]byte(expected), []byte(r.Header.Get("X-Webhook-Signature"))) {
    http.Error(w, "Invalid signature", http.StatusUnauthorized)
}
ruby
expected = "sha256=" + OpenSSL::HMAC.hexdigest(
  "SHA256",
  ENV.fetch("SENDSAR_WEBHOOK_SECRET"),
  request.raw_post
)

head :unauthorized unless ActiveSupport::SecurityUtils.secure_compare(
  expected,
  request.headers["X-Webhook-Signature"].to_s
)

Next steps

Sendsar — headless chat for B2B platforms