BookbagBookbag

Webhook guide

Receive real-time event callbacks from Bookbag: lead.created, conversation.escalated, conversation.replied, action.run, campaign.sent. How to register, the payload shape, and verifying the HMAC signature.

View as Markdown

Webhooks let Bookbag push events to your server the moment they happen — a new lead, an escalation, a campaign send — so you can sync a CRM, alert a channel, or trigger automation. This guide covers the developer workflow; for the dashboard setup screen see Integrations: Webhooks.

Register an endpoint

A webhook is a workspace-level URL plus the list of events it subscribes to and a signing secret. Create one in the dashboard, or via the management endpoint:

FieldTypeNotes
urlstringYour HTTPS endpoint. Required.
eventsarrayEvent names to subscribe to, or ["*"] for all. Defaults to all.
secretstringSigning secret. If you omit it, Bookbag generates one and returns it.
curl https://app.bookbag.ai/webhooks \
  -H "Content-Type: application/json" \
  -d '{ "url": "https://example.com/hooks/bookbag", "events": ["lead.created", "conversation.escalated"] }'
tip

Save the returned secret — you need it to verify signatures, and it is how you know a request really came from Bookbag.

Events

Bookbag fires these events. Subscribe to the ones you need, or * for all.

EventFires whenKey payload fields
lead.createdA lead is captured by an agent.leadId, agentId, fields
conversation.escalatedA conversation is handed off to a human / help desk.conversationId, target, transcript
conversation.repliedThe agent posts a reply in a conversation.conversationId and reply context
action.runAn agent action/tool runs.action run details
campaign.sentAn outbound campaign is sent.campaignId, agentId, recipients, message, channel

Payload shape

Every delivery is a POST with a JSON body of the form { event, data, ts }. event is the event name, data is the event-specific payload, and ts is the send time in epoch milliseconds.

{
  "event": "lead.created",
  "data": {
    "leadId": 42,
    "agentId": 123,
    "fields": { "email": "sam@acme.com", "name": "Sam" }
  },
  "ts": 1717286400000
}

Verify the signature

When a secret is set, Bookbag signs each delivery and sends the signature in the X-Bookbag-Signature header as sha256=<hex>. The signature is an HMAC-SHA256 of the raw request body keyed with your secret. Recompute it on your side and compare with a constant-time check — reject the request if it does not match.

import crypto from "node:crypto";

// Read the RAW body — verify before any JSON middleware reparses it.
app.post("/hooks/bookbag",
  express.raw({ type: "application/json" }),
  (req, res) => {
    const raw = req.body; // Buffer of the raw bytes
    const header = req.get("X-Bookbag-Signature") || "";
    const expected = "sha256=" + crypto
      .createHmac("sha256", process.env.BOOKBAG_WEBHOOK_SECRET)
      .update(raw)
      .digest("hex");

    const ok =
      header.length === expected.length &&
      crypto.timingSafeEqual(Buffer.from(header), Buffer.from(expected));
    if (!ok) return res.status(401).end();

    const { event, data } = JSON.parse(raw.toString("utf8"));
    // ... handle the event ...
    res.status(200).end();
  }
);
Verify against the raw body

Compute the HMAC over the exact bytes received, before any framework re-serializes the JSON. Re-stringifying can reorder keys or change whitespace and break the signature.

Best practices

  • Respond fast. Return 2xx quickly and process asynchronously; deliveries are best-effort.
  • Be idempotent. Treat handlers as at-least-once; de-duplicate on the natural id in data (e.g. leadId).
  • Always verify. Reject any request whose signature does not match your secret.
  • Use HTTPS. Bookbag only delivers to reachable, safe URLs.