Webhooks
Real-time event notifications pushed to your server when leads are created, tickets are updated, and more.
Overview
Webhooks push real-time event notifications to your server when things happen in your Next Cap account — a lead is captured, a ticket is created or updated, a conversation completes, etc.
call_request.created, call_request.updated, call_request.completed — fire whenever a visitor submits a callback through the widget or your team updates one. The payload data matches the /external/call-requests response, including any custom visitor-form fields your organization configured.help_center.*, help_collection.*, help_category.*, and help_article.* — fire whenever help center content changes. They power the incremental sync flow of the Helpdesk Content API.Setting Up Webhooks
You can create and manage webhooks from the Next Cap Dashboard or programmatically via the API using a Data API key.
From the Dashboard (recommended):
- Go to Settings → API Keys → Webhooks tab.
- Click Create Webhook.
- Enter your endpoint URL and select the events you want to receive.
- Copy the signing secret immediately — it is shown only once.
Alternatively, you can register webhooks via the API:
curl -X POST -H "X-Api-Key: sk_live_YOUR_KEY" \-H "Content-Type: application/json" \-d '{"url": "https://your-server.com/webhooks/nextcap", "events": ["lead.created", "ticket.created"]}' \https://api.nextcap.ai/api/v1/external/webhooks
{"data": {"id": "uuid","url": "https://your-server.com/webhooks/nextcap","secret": "a1b2c3...your-signing-secret...z9y8","events": ["lead.created", "ticket.created"],"is_active": true,"created_at": "2026-04-01T12:00:00.000Z"}}
secret is shown only once on creation. Store it securely — you will need it to verify webhook signatures.Event Types
lead.createdeventA new lead was captured (pre-chat form, in-chat, manual, or Telegram).
ticket.createdeventA new support ticket was created.
ticket.updatedeventA ticket was modified (any field change).
ticket.status_changedeventA ticket's status changed (e.g., open → resolved).
ticket.assignedeventA ticket was assigned or reassigned to an agent.
ticket.department_changedeventA ticket was transferred to a different department.
ticket.reopenedeventA resolved or closed ticket was reopened.
ticket.replyeventAn agent replied to a ticket (includes content and actor ID).
ticket.customer_replyeventA customer replied to a ticket via widget or email.
conversation.createdeventA new chat conversation was started.
conversation.completedeventA conversation was resolved.
conversation.handed_offeventA conversation was escalated to human support (entered handoff queue).
visitor.identifiedeventA visitor's identity was captured (email, name, etc.).
call_request.createdeventA visitor submitted a call-back request from the widget.
call_request.updatedeventA call request was modified — status change, assignment, or note added.
call_request.completedeventA call request reached the 'completed' status (callback finished).
help_center.createdeventA help center was created.
help_center.updatedeventA help center's settings changed (name, locales, domain, theme). A restore fires this with restored: true.
help_center.deletedeventA help center was deleted.
help_collection.createdeventA collection was added to a help center.
help_collection.updatedeventA collection changed, including AI translation fills (locales_filled).
help_collection.deletedeventA collection was deleted (children are promoted to its parent).
help_category.createdeventA legacy category was added under a collection.
help_category.updatedeventA legacy category changed.
help_category.deletedeventA legacy category was deleted.
help_article.createdeventAn article was created (payload includes its status).
help_article.updatedeventAn article changed, including AI translation fills (locales_filled).
help_article.publishedeventAn article transitioned to published — the typical 'rebuild my help site' trigger.
help_article.deletedeventAn article was soft-deleted; it stays fetchable with deleted_at set.
Payload Format
{"event": "lead.created","timestamp": "2026-04-01T12:05:00.000Z","data": {"id": "uuid","email": "john@acme.com","name": "John Doe","company": "Acme Inc","source": "pre_chat_form","created_at": "2026-04-01T12:05:00.000Z"}}
The data field uses the same shape as the corresponding REST API response for that entity.
Signature Verification
Every webhook request includes an X-Webhook-Signature header containing an HMAC-SHA256 hex digest of the raw request body, signed with your webhook secret.
const crypto = require("crypto");function verifyWebhook(body, signature, secret) {const expected = crypto.createHmac("sha256", secret).update(body).digest("hex");return crypto.timingSafeEqual(Buffer.from(signature),Buffer.from(expected),);}// In your Express handler:app.post("/webhooks/nextcap", (req, res) => {const sig = req.headers["x-webhook-signature"];if (!verifyWebhook(req.rawBody, sig, WEBHOOK_SECRET)) {return res.status(401).send("Invalid signature");}// Process the eventconsole.log(req.body.event, req.body.data);res.status(200).send("OK");});
import hmac, hashlibdef verify_webhook(body: bytes, signature: str, secret: str) -> bool:expected = hmac.new(secret.encode(), body, hashlib.sha256).hexdigest()return hmac.compare_digest(signature, expected)# In your Flask handler:@app.post("/webhooks/nextcap")def handle_webhook():sig = request.headers.get("X-Webhook-Signature")if not verify_webhook(request.data, sig, WEBHOOK_SECRET):abort(401)payload = request.jsonprint(payload["event"], payload["data"])return "OK", 200
Retries & Auto-Disable
Failed deliveries are retried 3 times with exponential backoff (5s, 30s, 2 min). After 10 consecutive failures, the webhook is automatically disabled. You can re-enable it by deleting and re-creating it.