Guide to Bitdefender Webhooks: Features and Best Practices
Bitdefender webhooks bring endpoint security events out of GravityZone as they happen: malware detected and quarantined, a phishing page blocked, a firewall rule triggered, ransomware activity stopped, an incident opened or reassigned, an agent installed or removed. GravityZone calls this the Event Push Service, and it is how security teams feed detections into a SIEM, a ticketing system or their own automation without polling the GravityZone API.
This guide covers how to turn the Event Push Service on for a generic HTTP collector, the JSON-RPC message format and event modules, how to authenticate pushes with the Authorization value you set, and the network and delivery details that trip up new collectors.
What are Bitdefender webhooks?
The Event Push Service sends GravityZone events to a web service you run. You configure it with the setPushEventSettings method of the GravityZone public API, choosing a service type (jsonRPC for your own HTTP collector, or one of the SIEM-specific types), the URL, an authorization value, and the event types you want.
With jsonRPC, every push is a JSON-RPC 2.0 call to a method named addEvents. Its params.events array holds one or more events, and each event's module field says what kind it is: av for an antimalware detection, fw for a firewall event, new-incident for an incident, and so on.
Bitdefender doesn't sign pushes with a shared secret you can check against a standard scheme. The authorization string you set in serviceSettings is sent as the Authorization header of every request, exactly as you entered it. Your collector compares it with the stored value.
Bitdefender webhook features
| Feature | Details |
|---|---|
| Configuration | setPushEventSettings in the GravityZone public API, one push configuration per company |
| Service type | jsonRPC for your own collector. Also splunk, cef, qradar, azureSentinel, azureSentinelV2 |
| Message | JSON-RPC 2.0, method: "addEvents", params.events array |
| Event identifier | module on each event |
| Authentication | serviceSettings.authorization, sent verbatim as Authorization |
| Body digest | Event-Push-Service-Md5 header (MD5-based, see below) |
| Transport | HTTPS with TLS 1.2 or higher |
| Certificate check | requireValidSslCertificate |
| Source IPs | 25 published GravityZone addresses, which the collector must allow |
| Acknowledgement | Any 2xx. Anything else, or no response, is an error |
| Testing and monitoring | sendTestPushEvent, getPushEventStats, resetPushEventStats |
| Retries and timeout | Not documented |
Common events
module | Event |
|---|---|
av | Antimalware detection |
aph | Antiphishing |
avc | Advanced Threat Control |
hd | Hyper Detect |
fw | Firewall |
dp | Data Protection |
uc | User Control and Content Control |
network-sandboxing | Sandbox Analyzer detection |
antiexploit | Antiexploit event |
network-monitor | Network Attack Defense event |
ransomware-mitigation | Ransomware activity detection |
new-incident, new-extended-incident | A new incident |
incident-status-changed, incident-assignee-changed, incident-priority-changed | An incident changes |
incident-note-created, incident-note-updated, incident-note-deleted | Incident notes |
install, uninstall, hwid-change | Agent lifecycle |
endpoint-moved-in, endpoint-moved-out | An endpoint is moved in or moved out |
modules, registration, task-status, troubleshooting-activity | Product and task status |
sva, sva-load, supa-update-status, security-container-update-available | Security Server and update status |
exchange-malware, exchange-user-credentials, exchange-organization-info | Exchange protection |
adcloud | Cloud Active Directory integration |
Bitdefender's Event Types reference lists a few more, such as partner-changed and integrations-hub-status. Here is the documented antiphishing push:
{
"jsonrpc": "2.0",
"method": "addEvents",
"params": {
"events": [
{
"companyId": "59a14b271da197c6108b4567",
"computer_name": "FC-EXCHANGE-01",
"computer_fqdn": "fc-exchange-01.fc.dom",
"computer_ip": "192.168.0.1",
"computer_id": "59b7d9bfa849af3a1465b7e4",
"product_installed": "BEST",
"aph_type": "phishing",
"url": "http://example.com/account/support/",
"status": "aph_blocked",
"last_blocked": "2017-09-14T08:49:43.000Z",
"count": 1,
"module": "aph"
}
]
},
"id": 1505378984190
}
The outer method is always addEvents, so it names the envelope, not the event. Route on each event's module, and expect one push to hold several events. Most endpoint events also carry companyId, computer_name and computer_id.
Inspect Bitdefender webhooks as they arrive. Point the Event Push Service at a Hookdeck Inspect URL and call
sendTestPushEventto see the exact JSON-RPC body and headers GravityZone sends, with no account or setup required.
Setting up Bitdefender webhooks
Choose the full Authorization value first, for example Bearer followed by the output of openssl rand -hex 32.
Then call setPushEventSettings with your GravityZone API key:
{
"jsonrpc": "2.0",
"method": "setPushEventSettings",
"id": "ad12cb61-52b3-4209-a87a-93a8530d91cb",
"params": {
"status": 1,
"serviceType": "jsonRPC",
"serviceSettings": {
"url": "https://example.com/webhooks/bitdefender",
"authorization": "Bearer YOUR_LONG_RANDOM_VALUE",
"requireValidSslCertificate": true
},
"subscribeToEventTypes": {
"av": true,
"aph": true,
"fw": true,
"new-incident": true,
"incident-status-changed": true
}
}
}
status is 1 to enable the service and 0 to disable it. Subscribe only to the modules you will handle. Use getPushEventSettings to read the configuration back and sendTestPushEvent to send a test.
Your collector must accept HTTPS with TLS 1.2 or higher and allow GravityZone's egress IPs, which Bitdefender lists on the setPushEventSettings page. Keep requireValidSslCertificate set to true in production.
To route through Hookdeck, create a source with the Bitdefender type and use its URL as serviceSettings.url. The source has one field, Authorization Header. As Hookdeck's setup note says, paste the authorization value exactly as set in GravityZone, including any Bearer or Basic prefix. Hookdeck compares the whole header with it and accepts POST.
Securing Bitdefender webhooks
Compare the whole Authorization header with the value you set in serviceSettings.authorization, in constant time. Don't strip the prefix or decode it.
const crypto = require('crypto');
const express = require('express');
// Exactly what you set in serviceSettings.authorization, e.g. "Bearer 3f9c...".
const EXPECTED = process.env.BITDEFENDER_AUTHORIZATION;
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/bitdefender', express.raw({ type: '*/*' }), (req, res) => {
const received = req.get('authorization');
if (!EXPECTED || typeof received !== 'string' || !safeEqual(received, EXPECTED)) {
return res.sendStatus(401);
}
let call;
try {
call = JSON.parse(req.body.toString('utf8'));
} catch {
return res.sendStatus(400);
}
if (call.method !== 'addEvents' || !Array.isArray(call.params?.events)) {
return res.sendStatus(400);
}
// Queue each event; a worker routes on event.module.
res.sendStatus(200);
});
The same check in Python:
import hmac
import os
EXPECTED = os.environ.get("BITDEFENDER_AUTHORIZATION", "") # e.g. "Bearer 3f9c..."
def verify_bitdefender_request(headers) -> bool:
if not EXPECTED:
return False # never accept when unconfigured
received = headers.get("authorization")
if not received:
return False
# Compare bytes: compare_digest raises TypeError on a non-ASCII str.
return hmac.compare_digest(received.encode("utf-8"), EXPECTED.encode("utf-8"))
A static Authorization value authenticates the sender, not the body, and with no timestamp a captured push can be replayed and still pass. Use HTTPS (GravityZone requires TLS 1.2 or higher anyway), a long random value, and the published IP allowlist as a second check. Rotate the value with setPushEventSettings if it might have leaked.
Bitdefender also documents an Event-Push-Service-Md5 header, described as md5(api_; its PHP example concatenates the API key with the hex MD5 of the raw body and hashes the result. That ties the header to the body, but it relies on MD5, the page doesn't say exactly which key is meant, and its sample uses two different variable names for the key. If you want to use it, confirm it against a captured push first. Hookdeck's Bitdefender source doesn't check it.
Make Bitdefender webhooks production-ready. Hookdeck Event Gateway checks the
Authorizationvalue, answers GravityZone with a2xxas soon as a push arrives, and queues every detection durably for your SIEM or automation.
Bitdefender webhook limitations and pain points
A collector GravityZone can't reach
The Problem: Pushes never arrive, or getPushEventStats shows errors, even though the collector works from your own machine.
Why It Happens: GravityZone sends only from its published IP addresses, requires TLS 1.2 or higher, and validates certificates when requireValidSslCertificate is true.
Workarounds:
- Allow every listed GravityZone IP at your firewall and load balancer.
- Serve the collector with a valid certificate on a public address.
- Check
getPushEventStatsaftersendTestPushEvent.
How Hookdeck Can Help: A Hookdeck source URL is public, serves a valid certificate and supports modern TLS, so GravityZone can reach it without firewall changes on your side. Hookdeck then delivers to your collector over the network path you control.
Undocumented retry behaviour
The Problem: A detection pushed while your collector was down may never arrive.
Why It Happens: Bitdefender documents that anything other than a 2xx, or no response, counts as an error, but not whether or when a failed push is retried.
Workarounds:
- Return a
2xxas soon as the push is stored, and process it afterwards. - For anything critical, cross-check against GravityZone reports or the API.
How Hookdeck Can Help: Hookdeck stores each push as it arrives and delivers it to your collector with retries on your schedule, and you can replay any push after a fix.
Batches of mixed events
The Problem: A handler written for one event per request misses detections, or fails a whole batch on one unexpected module.
Why It Happens: params.events is an array, and one push can mix modules.
Workarounds:
- Iterate over every event and route each on its
module. - Acknowledge modules you don't handle instead of rejecting the push.
How Hookdeck Can Help: A transformation can reshape a push before delivery, and the original request stays in Hookdeck for replay.
The prefix belongs to the value
The Problem: Every push fails your check because you stored the token without Bearer , or decoded a Basic value.
Why It Happens: GravityZone sends the authorization string exactly as you set it, prefix and all.
Workarounds:
- Store the full string you sent in
setPushEventSettingsand compare it whole.
How Hookdeck Can Help: Hookdeck's Bitdefender source compares the whole Authorization value you paste. Hookdeck answers GravityZone with a 200 either way, but a push with a missing or wrong value is recorded as failed verification and never reaches your collector.
Best practices
Subscribe to what you handle
subscribeToEventTypes takes one flag per module. Turn on only the modules your collector processes, so incident and agent noise doesn't hide detections.
Deduplicate on event content
GravityZone events have no single documented event ID. Build a key from module, computer_id and the event's own timestamp fields where present. See our guide to webhook idempotency.
Keep certificate validation on
Set requireValidSslCertificate to true outside test environments.
Monitor push statistics
Call getPushEventStats regularly, or alert on it, so a failing collector doesn't go unnoticed.
Acknowledge fast and process asynchronously
Store the push and return a 2xx, then enrich and forward the events in the background. See why to process webhooks asynchronously.
Conclusion
The GravityZone Event Push Service sends batches of security events as JSON-RPC addEvents calls, authenticated by the Authorization value you set with setPushEventSettings. Compare that whole value in constant time, route each event on its module, allow GravityZone's IPs over TLS 1.2 or higher, and return a 2xx quickly, because Bitdefender doesn't document what happens to a push you fail.
Hookdeck Event Gateway checks the header, stores every push as it arrives, and delivers it to your collector with retries and replay.
Get started with Hookdeck for free and handle Bitdefender webhooks reliably in minutes.