Webhooks
Subscribe your server to Ken events and receive a fully-loaded JSON payload the instant something happens in a campaign - no polling required.
Overview
Webhooks let you connect Ken to any external system - a CRM, a sales tool, a Slack bot, or your own backend - by registering an HTTPS endpoint. When a subscribed event occurs in one of your campaigns, Ken sends a signed HTTP POST to that URL with a single self-contained JSON body containing the contact, company, campaign, and the event-specific data.
This is useful for any team that needs near-real-time visibility into campaign activity outside of Ken: routing interested leads into a CRM the moment they reply, suppressing unsubscribes in a marketing list, logging clicks, or triggering internal alerts.
Webhooks are found under Settings - Integrations - Webhooks. Open Settings from the main navigation, click the Integrations tab, then select Webhooks from the sub-navigation.
Before you start
- You need a workspace role of Member or higher. View-only users cannot manage webhook endpoints. The page will show a "not allowed" message if your role is insufficient.
- Your endpoint must be reachable over public HTTPS and must respond with a 2xx status within a few seconds of each delivery.
- If you see a "Webhooks aren't enabled yet" notice, the feature is still being provisioned for your account. The event reference below the notice shows exactly what payloads you will receive once it is live.
How to register an endpoint
- Open Settings, click the Integrations tab, and select Webhooks.
- Click Add endpoint in the top-right corner.
- In the Add webhook endpoint dialog, fill in the fields:
- Endpoint URL - the public HTTPS URL Ken will POST to (for example,
https://yoursite.com/webhooks/ken). - Events - check each event type you want to receive. At least one event is required.
- Description (optional) - a label to identify the endpoint in the table (for example,
Production CRM sync).
- Endpoint URL - the public HTTPS URL Ken will POST to (for example,
- Click Add endpoint to save.
The endpoint appears in the table with an Enabled status badge.
Event types
| Event | Type string | When it fires |
|---|---|---|
| Email sent | email_sent | A sequence email was sent to a lead, including each follow-up step. |
| Lead replied | lead_replied | A lead sent a genuine human reply (auto-replies and out-of-office messages are excluded). |
| Lead interested | lead_interested | A reply was classified as interested - the highest-intent signal. |
| Lead unsubscribed | lead_unsubscribed | A lead opted out of further emails. |
| Lead clicked | lead_clicked | A lead clicked a tracked link in a campaign email. |
A single positive reply fires both lead_replied and lead_interested. Subscribe to lead_replied to see every conversation, or lead_interested alone to see only hot leads.
Edit or disable an endpoint
Open the row menu (the three-dot icon on the right of any row) and choose:
- Edit - change the URL, events, or description.
- Disable / Enable - pause and resume delivery without deleting the endpoint.
- Delete - permanently remove the endpoint and stop all deliveries.
View delivery history
- Click the three-dot menu on any endpoint row.
- Choose View deliveries.
- A side panel opens showing each delivery attempt with its status (success, pending, or fail), event type, timestamp, and HTTP response code.
- To retry a failed delivery, click Resend on that row.
Retrieve the signing secret
- Click the three-dot menu on any endpoint row.
- Choose Reveal signing secret.
- Click the eye icon to reveal the value, or click Copy to copy it directly.
- Store the secret server-side - never expose it in client-side code.
How it works
Every incoming request from Ken carries three headers: svix-id, svix-timestamp, and svix-signature. Verify these headers before processing the event body. Use your endpoint's signing secret (which starts with whsec_) and the official svix library:
// npm install svix
import { Webhook } from 'svix';
const wh = new Webhook(process.env.KEN_WEBHOOK_SECRET);
// Always pass the RAW request body - re-serializing breaks the signature.
app.post('/webhooks/ken', express.raw({ type: 'application/json' }), (req, res) => {
let payload;
try {
payload = wh.verify(req.body, {
'svix-id': req.headers['svix-id'],
'svix-timestamp': req.headers['svix-timestamp'],
'svix-signature': req.headers['svix-signature'],
});
} catch {
return res.status(400).send('Invalid signature');
}
// handle payload.eventType here ...
res.sendStatus(200);
});
Payload shape. Every delivery is a JSON object. The contact, company, and campaign blocks are always present. Additional blocks appear depending on the event type:
email- present onemail_sentand the reply events.reply- present onlead_repliedandlead_interested.click- present onlead_clicked.
Delivery semantics. Ken uses at-least-once delivery with exponential-backoff retries. If your endpoint returns a non-2xx status or times out, Ken retries automatically. A delivery log going back 30 or more days is available via "View deliveries". You can replay any individual delivery from that log.
Deduplication. Each event carries a unique eventId in the form {clientId}:{eventType}:{sourceEventId}. Use this as your idempotency key to handle duplicate deliveries safely.
Click payload field names. click.originalUrl is the final destination URL; click.destinationUrl is the short tracking link that was clicked. The naming is counterintuitive - rely on the field descriptions in the event reference panel on the Webhooks page.
reply.confidence is always null. A classifier confidence score is not yet available. This is a documented gap, not a broken field.
Troubleshooting and debugging
Symptom: "Webhooks aren't enabled yet" banner is shown.
- Cause: Webhook delivery has not been provisioned for your account yet.
- What to do: Contact your Ken team. The event reference on the page is still useful for planning your integration.
Symptom: The "Add endpoint" button is missing.
- Cause: The "Webhooks aren't enabled yet" banner is active. The button is hidden while the feature is not provisioned.
- What to do: Same as above.
Symptom: "Your role is not allowed to manage webhooks for this workspace."
- Cause: Your workspace role is View-only.
- What to do: Ask an Admin to adjust your role, or ask them to create the endpoint on your behalf.
Symptom: Deliveries are failing with non-2xx responses.
- Cause: Your endpoint is returning an error, timing out, or is unreachable.
- What to check: Open "View deliveries" to see the HTTP response code. Confirm your endpoint is publicly reachable, accepts POST requests, and responds within a few seconds. Check your server logs for application errors.
- How to fix: Correct the issue on your endpoint, then click Resend on each failed delivery to replay it.
Symptom: Signature verification is failing.
- Cause: Most commonly the raw request body is not being used - parsing it before verification breaks the HMAC.
- What to check: Make sure your framework reads the raw bytes before JSON-parsing. In Express, use
express.raw({ type: 'application/json' })on the webhook route. - Also check: Confirm you are using the correct signing secret for this specific endpoint. Each endpoint has its own secret. Retrieve it via "Reveal signing secret".
Symptom: Events are arriving twice.
- Cause: At-least-once delivery means retries can produce duplicates, especially if your endpoint acknowledged slowly or returned a transient error.
- How to fix: Key your processing logic on
eventId. Discard any event whoseeventIdyou have already processed.
Symptom: Deliveries appear in the log but your CRM is missing some records.
- Cause: The delivery succeeded (HTTP 200) but your handler threw an error after acknowledging.
- What to check: Acknowledge (return 200) immediately, then process asynchronously. If you process synchronously and your handler errors after the response is sent, Ken sees success but your side drops the event.
Symptom: A lead_interested delivery arrived but no lead_replied did.
- Cause: This should not happen. Both events fire for the same reply. If only one arrived, check your endpoint's subscribed events - you may have subscribed to
lead_interestedbut notlead_replied.
FAQ
Can I subscribe to all events on one endpoint?
Yes. Select all five event types when creating or editing the endpoint. Use the eventType field in the payload to branch your handler logic.
Can I register multiple endpoints? Yes. You can have as many endpoints as you need - for example, one for your CRM, one for a logging service, and one for testing.
What happens to deliveries while my server is down? Ken retries with exponential backoff. Once your server is back up, pending deliveries will be attempted again. You can also trigger a manual resend from "View deliveries" for any failed delivery.
Can I register endpoints via the API instead of the UI?
Yes. Send a POST to /v1/webhooks/endpoints with the same fields the form uses (url, events, description). An empty events array is rejected. The API reference snippet is available in the "Registering an endpoint via the API" section on the Webhooks page itself.
Is there a way to test an endpoint before going to production? Use a service like webhook.site or a local tunnel (for example, ngrok) to receive a test delivery. Register that temporary URL as an endpoint, trigger an action in a campaign that fires the event you want to test, then check the delivery log to confirm the payload arrived correctly.
How far back does the delivery log go? The delivery log retains at least 30 days of history. You can resend any delivery in that window from the "View deliveries" panel.