D Diagent docs

API reference

Outgoing webhooks

Outgoing webhooks let Pitchbar push events to your endpoint when something interesting happens. Configure them per workspace under /app/integrations/webhooks.

v1 surface โ€” small but stable
Today only one event ships: lead.captured. The delivery is single-attempt (no retries) with HMAC-SHA256 signing. Conversation-level events (conversation.started, conversation.message, conversation.routed) are deferred and not yet emitted.

Configuration

Each webhook subscription has:

  • URL โ€” your endpoint. HTTPS strongly recommended.
  • Events โ€” currently only lead.captured.
  • Signing secret โ€” auto-generated. Used to HMAC the body.
  • Active โ€” toggle.

Headers

The dispatcher sends two headers:

HeaderValue
Content-Typeapplication/json
X-Pitchbar-Signaturet={timestamp},v1={hmac} โ€” Stripe-style timestamped signature

Signature verification

The signature is an HMAC-SHA256 of "{timestamp}.{body}" using the subscription's signing secret. To verify:

// Node
const crypto = require('crypto');

function verify(rawBody, signatureHeader, secret) {
    const parts = Object.fromEntries(
        signatureHeader.split(',').map(p => p.split('='))
    );
    const ts = parts.t;
    const sig = parts.v1;

    const expected = crypto
        .createHmac('sha256', secret)
        .update(`${ts}.${rawBody}`)
        .digest('hex');

    return crypto.timingSafeEqual(
        Buffer.from(expected),
        Buffer.from(sig)
    );
}

Always use a constant-time comparison (timingSafeEqual in Node, hash_equals in PHP) to avoid timing attacks. Reject the delivery if t is older than ~5 minutes โ€” replay protection lives on your side.

Delivery semantics

Each delivery is a single HTTP POST with a 5-second timeout. There is no built-in retry: a non-2xx response or timeout drops the event. If your endpoint is briefly down you'll lose that event. Recommended pattern:

  • Reply 2xx fast. Buffer to your own queue and process asynchronously.
  • Idempotency. Use the event's occurred_at + data contents to dedupe โ€” there's no per-delivery ID yet.
  • Reconciliation. For business-critical data, periodically pull from the admin lead list rather than relying solely on webhooks.

Event payloads

lead.captured

{
    "event": "lead.captured",
    "occurred_at": "2026-05-07T12:00:00Z",
    "data": {
        "lead_id": "01HXY...",
        "agent_id": "01HXY...",
        "conversation_id": "01HXZ...",
        "name": "Alex",
        "email": "[email protected]",
        "phone": "+1...",
        "fields": { "company": "Acme" }
    }
}

The exact field set depends on what the visitor filled into the inline form and any custom fields you've defined on the agent. Keys are stable; missing values are null rather than absent.

Stripe webhooks (incoming)

These are separate โ€” Stripe sends to /billing/webhook and Cashier verifies the standard Stripe-Signature header using STRIPE_WEBHOOK_SECRET. They drive subscription state. You don't configure these from the integrations page; they're a platform-admin concern.

Testing locally

Point a webhook at https://webhook.site or a tunneled local URL (ngrok). Submit a lead via the playground or the live widget; the webhook fires within a second.

Roadmap

The webhook surface will expand to include conversation-level events, a per-delivery ID, and at-least-once retry semantics. Until then, poll the admin endpoints for state you care about beyond lead.captured.