Skip to content
Webhooks

Events & Payloads

The full event catalog with payload shapes and signature verification.

On this page

Webhook Events#

Receive real-time notifications when chat events occur — messages, sessions, and visitor identity updates — delivered to your HTTPS endpoints via HTTP POST with HMAC-SHA256 signatures.

Not to be confused with the Conversations API callback. Webhook events are push notifications for activity on your chatbot widgets. The callback URL on API keys is for receiving async AI responses from the Conversations API.

Setting Up Webhooks#

Create webhook endpoints in Console → Settings → Developer → Webhook Endpoints. Each endpoint requires:

  • An HTTPS URL that accepts POST requests
  • One or more event types to subscribe to
  • An optional widget scope to filter events from a specific widget

A signing secret is automatically generated when you create an endpoint. Store it securely — you will need it to verify webhook signatures.

Event Types#

EventTrigger
message.completedAI response sent successfully
message.failedMessage processing error
session.createdNew or resumed chat session
session.endedSession resolved or expired
session.ratedVisitor rated a session
visitor.identifiedVisitor identity fields updated
knowledge.upsert.completedIntegration API upsert batch finished
knowledge.reconcile.completedIntegration API feed reconcile finished

Payload Format#

Every webhook delivery is an HTTP POST with a JSON body:

{
  "eventId": "01HX7V...",
  "event": "message.completed",
  "widgetId": "6650a1b2c3d4e5f6a7b8c9d0",
  "sessionId": "sess_abc123",
  "timestamp": "2025-01-15T10:30:00.000Z",
  "data": {
    "messageId": "msg_xyz789",
    "content": "Here are some products under $50...",
    "products": [
      { "id": "prod_1", "name": "Wireless Earbuds", "score": 0.95 }
    ],
    "sessionEnded": false,
    "usage": { "inputTokens": 1234, "outputTokens": 567 }
  }
}

Event Data#

Each event type includes different fields in the data object:

message.completed#

{
  "messageId": "msg_abc123",
  "content": "Here are some products...",
  "products": [{ "id": "prod_1", "name": "Wireless Earbuds", "score": 0.95 }],
  "sessionEnded": false,
  "credits": {
    "chat": 8,
    "retrieval": 4,
    "orchestration": 2,
    "embedding": 1,
    "rerank": 0,
    "plugin": 0,
    "crawler": 0
  },
  "usage": { "inputTokens": 1234, "outputTokens": 567 }
}

message.failed#

{
  "error": {
    "code": "CREDITS_EXHAUSTED",
    "message": "Account credits have been exhausted"
  }
}

session.created#

{
  "status": "active",
  "createdAt": "2025-01-15T10:30:00.000Z",
  "visitorId": "vis_abc123",
  "resumed": false
}

session.ended#

{
  "status": "resolved",
  "resolvedAt": "2025-01-15T11:00:00.000Z",
  "resolution": "manual"
}

session.rated#

{
  "rating": "positive"
}

visitor.identified#

{
  "visitorId": "vis_abc123",
  "fields": ["name", "email"],
  "context": {
    "name": "John",
    "email": "john@example.com"
  }
}

knowledge.upsert.completed#

{
  "knowledgeBaseId": "6650a1b2c3d4e5f6a7b8c9d0",
  "knowledgeBaseType": "product",
  "operation": "upsert",
  "matchBy": "sku",
  "created": 12,
  "updated": 38,
  "unchanged": 48,
  "failed": 2,
  "requestId": "req_01HX7V..."
}

knowledge.reconcile.completed#

{
  "knowledgeBaseId": "6650a1b2c3d4e5f6a7b8c9d0",
  "knowledgeBaseType": "product",
  "operation": "reconcile",
  "matchBy": "sku",
  "deleted": 5,
  "unchanged": 995,
  "requestId": "req_01HX8A..."
}

Verifying Signatures#

Every delivery includes three headers for signature verification:

HeaderDescription
X-Gydr-SignatureHMAC-SHA256 signature of {timestamp}.{body}
X-Gydr-TimestampUnix epoch seconds when the delivery was signed
X-Gydr-EventEvent type (e.g., message.completed)

To verify a webhook, compute the HMAC-SHA256 of the timestamp and raw request body bytes using your endpoint's signing secret, then compare it to the signature header. Always verify the raw bytes — re-serialising a parsed JSON object risks key-order or whitespace differences that break the comparison:

import crypto from "crypto"

// Mount this route with express.raw() so req.body is the unmodified Buffer:
// app.post("/webhook", express.raw({ type: "application/json" }), handler)

function verifyWebhook(req, secret) {
  const signature = req.headers["x-gydr-signature"]
  const timestamp = req.headers["x-gydr-timestamp"]
  // req.body is a Buffer when using express.raw()
  const rawBody = Buffer.isBuffer(req.body)
    ? req.body.toString("utf8")
    : req.body

  const expected = crypto
    .createHmac("sha256", secret)
    .update(`${timestamp}.${rawBody}`)
    .digest("hex")

  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expected)
  )
}

Tip: Always use a timing-safe comparison function to prevent timing attacks. In Node.js, use crypto.timingSafeEqual(). In Python, use hmac.compare_digest().

Delivery Behavior#

  • Deliveries timeout after 10 seconds — your endpoint must respond within this window
  • Failed deliveries are attempted a total of 3 times at 0 s, 1 s, and 5 s intervals
  • A fresh HMAC signature is generated for each retry attempt
  • Your endpoint must return a 2xx status code to acknowledge receipt
  • After all retries fail, the event is sent to a dead letter queue for monitoring

Security Requirements#

  • Webhook URLs must use HTTPS — HTTP is not accepted
  • Private IP addresses and localhost are blocked for SSRF protection
  • Redirects are blocked — your URL must respond directly
  • Rotate your signing secret periodically via the Console

Widget Scoping#

By default, a webhook endpoint receives events from all widgets in your account. To limit an endpoint to events from a specific widget, select the widget when creating or editing the endpoint. You can create multiple endpoints with different widget scopes to route events to different systems.

Best Practices#

  • Respond quickly — return a 200 status immediately and process the event asynchronously
  • Handle duplicates — use the eventId field for idempotency
  • Verify signatures — always validate the HMAC signature before processing
  • Check timestamps — reject events with timestamps more than 5 minutes old to prevent replay attacks
  • Log failures — monitor your endpoint for errors to catch issues early

We use cookies to run and improve Gydr.

Read our Cookie Policy