Guide to KnowBe4 Webhooks: Features and Best Practices
KnowBe4 webhooks send security awareness data to the other tools your security team uses. The KSAT console can post phishing, training and general events as they happen, for example to a data analysis platform such as Splunk, and PhishER can send reported emails to an automation platform such as Tines when a triage action runs.
This guide covers the two kinds of KnowBe4 webhook, what KnowBe4 does and doesn't document about them, how to authenticate them with a secret custom header, KSAT's retry and deactivation rules, and the practices that make a KnowBe4 receiver dependable.
What are KnowBe4 webhooks?
KnowBe4 has two separate webhook features:
- KSAT console webhooks send real-time phishing and training data. You create a webhook in the KSAT console's account settings and choose which events it receives. They are limited to Platinum, Diamond and SAT Advanced customers.
- PhishER webhooks send data about reported messages. You create a webhook in PhishER's settings, then build a PhishER action with the Send to Webhook option. When the action runs, the reported messages it affects are sent to the webhook.
Both let you add Custom Data, static key/value pairs included in the payload, and Custom Headers. Neither product signs its requests. Each offers an authentication setting, but KnowBe4 doesn't document the headers those options send, so the dependable way to authenticate is a custom header with a secret value that your receiver compares.
KnowBe4 webhook features
| Feature | KSAT console | PhishER |
|---|---|---|
| Configuration | Account Settings > Account Integrations > Webhooks > + Create | Settings > Webhook > New Webhook |
| Availability | Platinum, Diamond and SAT Advanced | PhishER customers |
| What triggers it | Selected Phishing, Training and General events | A PhishER action with Send to Webhook |
| Authentication option | None, Bearer Token, Basic or API Key | No Auth, API Key, Bearer Token or Basic Auth |
| Custom headers and data | Yes | Yes |
| Signature and timestamp | None | None |
| Payload schema | Not published | Not published, shaped by the Available Data types you select |
| Retries | Up to 3 more attempts, 15 minutes apart | Not documented. Optional Success or Failure message tags |
| Deactivation | More than 1,000 failed responses in an hour | Not documented |
| HTTP method and source IPs | Not documented | Not documented |
Common events
KnowBe4 doesn't publish event names or payloads for either product.
In KSAT, the Events field groups events into Phishing Events, Training Events and General Events. You can tick a whole group or open it and pick individual events. The documentation names one, Phish Failure Removed: its payload includes the email address of the admin who removed the phishing failure, whether from a user's timeline or from Phishing Campaign Overview > Users, or the impersonation email address if the admin was impersonating someone.
In PhishER, there is no event to choose. You select Available Data types on the webhook, and the payload carries that data for each reported message an action sends. The Addresses type sends every email address on the message, including From, To, CC, Reply To and Reported By, which matters if the destination shouldn't hold personal data.
Because neither product documents a type field, use the Custom Data option to label requests yourself. A KSAT webhook with custom data source = ksat-phishing puts that pair in every payload, and your receiver can route on it.
Inspect KnowBe4 webhooks as they arrive. Point a KSAT or PhishER webhook at a Hookdeck Inspect URL to see the exact method, headers and body KnowBe4 sends, with no account or setup required.
Setting up KnowBe4 webhooks
Pick a header name and a long random value first, for example X-Webhook-Secret with the output of openssl rand -hex 32.
For a KSAT webhook:
- In your KSAT admin account, click your email address at the top right and select Account Settings.
- Go to Account Integrations > Webhooks and click + Create.
- Enter a Name and your HTTPS endpoint as the URL.
- Turn on Enable Webhook Failure Notifications so someone hears about failures before the webhook is deactivated.
- Under Custom Header, add your header name as the Key and the secret as the Value.
- Optionally add Custom Data to label the payload.
- Set Status to Active, tick the Events you want, and click Save.
For a PhishER webhook:
- Go to Settings > Webhook and click New Webhook.
- Enter a Name and your HTTPS endpoint as the URL.
- Select the Available Data types to send.
- Under Custom Headers, add your header name and secret.
- Optionally set Message Tags for success and failure, then click Create.
- Create a PhishER action that uses Send to Webhook with this webhook.
With Hookdeck. Create a Hookdeck source with the KnowBe4 type and use its URL as the webhook URL. Enter the API Key Header and API Key exactly as you set them as the custom header, including any Bearer prefix. The source compares the whole value. It pre-selects POST but doesn't manage the method, because KnowBe4 doesn't state one, so check the first delivery in Hookdeck and, if it uses another method, allow that method on the source too.
You could instead name the header that the Bearer Token or API Key option sends, once you've seen it on a real delivery. Basic authentication isn't covered by Hookdeck's KnowBe4 source.
Securing KnowBe4 webhooks
Look up your custom header case-insensitively and compare the whole value with the one you stored, in constant time.
const crypto = require('crypto');
const express = require('express');
const HEADER = (process.env.KNOWBE4_WEBHOOK_HEADER || 'X-Webhook-Secret').toLowerCase();
const EXPECTED = process.env.KNOWBE4_WEBHOOK_SECRET;
function safeEqual(received, expected) {
const a = Buffer.from(received, 'utf8');
const b = Buffer.from(expected, 'utf8');
// timingSafeEqual throws on unequal lengths, so check first.
return a.length === b.length && crypto.timingSafeEqual(a, b);
}
const app = express();
app.post('/webhooks/knowbe4', express.raw({ type: '*/*' }), (req, res) => {
const received = req.headers[HEADER]; // Node lower-cases header names
if (!EXPECTED || typeof received !== 'string' || !safeEqual(received, EXPECTED)) {
return res.sendStatus(401);
}
let payload;
try {
payload = JSON.parse(req.body.toString('utf8'));
} catch {
return res.sendStatus(400);
}
// No documented event field: route on the Custom Data you added, e.g. payload.source.
res.sendStatus(200);
});
The same check in Python:
import hmac
import os
def verify_knowbe4_request(headers) -> bool:
secret = os.environ.get("KNOWBE4_WEBHOOK_SECRET")
if not secret:
return False # never accept when unconfigured
name = os.environ.get("KNOWBE4_WEBHOOK_HEADER", "X-Webhook-Secret")
received = headers.get(name) # case-insensitive in Starlette and Flask
if not received:
return False
return hmac.compare_digest(received.encode("utf-8"), secret.encode("utf-8"))
A static header proves the sender knows the secret. It does not cover the body, and there is no timestamp, so a captured request can be replayed and still pass. Serve the endpoint over HTTPS only, use a long random value, keep it out of logs, and change it on the webhook and in your environment together if it might have leaked.
Make KnowBe4 webhooks production-ready. Hookdeck Event Gateway checks your custom header on every request, keeps every phishing and training event durably, and answers KnowBe4 straight away so a failing service doesn't get your KSAT webhook deactivated.
KnowBe4 webhook limitations and pain points
KSAT deactivates failing webhooks
The Problem: During an incident on your side, the KSAT webhook switches itself off, and events stop arriving until an admin sets it back to Active.
Why It Happens: KSAT deactivates a webhook that receives more than 1,000 failed responses in an hour. With Enable Webhook Failure Notifications on, recipients get an email at 50 percent of that limit and another when it's deactivated.
Workarounds:
- Turn on failure notifications and send them to a monitored address.
- Return a success response as soon as the header checks out, and process afterwards.
- After an outage, set the webhook back to Active in Account Settings and review in the console what happened while it was off.
How Hookdeck Can Help: Hookdeck answers KnowBe4 as soon as it has stored the request, so failures in your service don't count towards KSAT's deactivation limit. Delivery to your service then uses retries you configure, and anything that failed can be replayed.
Short KSAT retries, none documented for PhishER
The Problem: An outage of an hour or so loses KSAT events, and a PhishER delivery that fails may only show up as a failure tag on the message.
Why It Happens: KSAT retries a failed request up to three more times, 15 minutes apart. PhishER's article describes Success and Failure tags but no retries.
Workarounds:
- Set a Failure tag in PhishER and review tagged messages after any incident.
- Check the KSAT console for activity you expected to receive after any incident on your side.
How Hookdeck Can Help: Because Hookdeck accepts each request immediately, KnowBe4's retries are rarely needed. Hookdeck's own retries and issues take over between Hookdeck and your service.
No published payloads
The Problem: You can't write a parser from the docs, and nothing in the request says which event or action produced it.
Why It Happens: KnowBe4 documents how to configure webhooks, not what they send. KSAT events are chosen from UI groups, and PhishER payloads depend on the data types selected.
Workarounds:
- Capture real deliveries for each event or data type you use before writing handlers.
- Add Custom Data to each webhook so the payload names its source.
How Hookdeck Can Help: Hookdeck stores every request with its headers and body, so you can capture examples once and replay them against your handler while you build it. Filters can route on the custom data you add.
Sensitive data in transit
The Problem: PhishER payloads can include every email address on a reported message, and KSAT sends your users' phishing and training data.
Why It Happens: That's the data these webhooks exist to send.
Workarounds:
- Select only the Available Data types the destination needs.
- Use HTTPS, restrict access to the endpoint's logs, and apply your retention policy to stored payloads.
How Hookdeck Can Help: Hookdeck transformations can remove fields a destination shouldn't receive before delivery.
Best practices
Label every webhook with Custom Data
A static key such as source with values like ksat-phishing, ksat-training or phisher gives you the routing field KnowBe4 doesn't provide.
Prefer a custom header for authentication
Custom headers are documented with an explicit key and value in both products, so you know exactly what will arrive. Use a long random value.
Keep failure notifications on
In KSAT, the failure email is your warning that deactivation is coming. Make sure it goes to a shared, monitored address.
Acknowledge fast and process asynchronously
Verify the header, store the payload, respond, and push to your SIEM or SOAR from a background job. See why to process webhooks asynchronously.
Make handlers idempotent
Retries resend the same event. Derive a key from fields you see in captured payloads, such as a user, campaign and timestamp, and skip repeats. See our guide to webhook idempotency.
Conclusion
KnowBe4 sends unsigned webhooks from KSAT for phishing, training and general events, and from PhishER when a Send to Webhook action runs, with no published payload schema. Add a secret custom header and compare it whole in constant time over HTTPS, label each webhook with custom data, capture real payloads before you build handlers, and keep failure notifications on.
Hookdeck Event Gateway checks the header, answers KnowBe4 immediately, and keeps every event until your service has processed it, so a bad hour on your side doesn't deactivate a KSAT webhook.
Get started with Hookdeck for free and handle KnowBe4 webhooks reliably in minutes.