Attack webhooks
What we POST to your endpoint when an attack starts and when it ends.
We POST to a URL you own when an attack on your IP space starts, and again when
it ends. Webhooks are configured under Organisation → Alerting in the portal —
they need no API key, no ability, and none of the
activation flags the API endpoints require.
This page covers the two attack events. The same editor also subscribes to attack summaries (24-hour, 7-day and 30-day roll-ups), billing and maintenance events, which are not documented here.
Which webhooks receive an event
A webhook is delivered an event when all of these hold:
- it is enabled
- the event is in its subscription list
- it belongs to an organisation resolved for the attacked address — every ASN owner of a containing prefix, plus any customer holding that address on a service (see Ownership)
Choose the type by what is reading it
| Type | For |
|---|---|
generic | Code. The only shape with machine-readable numbers and full attack detail |
discord | A Discord channel. Pre-rendered embed |
slack | A Slack channel. Pre-rendered attachment |
Do not parse the Discord or Slack shapes. Their figures are formatted for people
(10 Gbps, 1 Mpps) and wrapped in code fences, the Discord embed carries no
attack_type, top_sources or top_ports, and the field names differ between
the start and end events. Point automation at a generic webhook.
The generic payload
{
"event": "attack_detected",
"is_test": false,
"timestamp": "2026-08-19T14:02:11+00:00",
"data": {
"target_ip": "192.0.2.1",
"target_prefix": "192.0.2.0/24",
"attack_type": "UDP Flood",
"peak_pps": 1000000,
"peak_bps": 10000000000,
"top_sources": ["198.51.100.7", "203.0.113.19"],
"top_ports": [27015, 53],
"started_at": "2026-08-19T14:02:11+00:00"
}
}attack_ended is the same body with a different event. timestamp is when we
delivered, not when anything happened to your traffic — data.started_at is the
attack's own clock.
attack_type is a protocol label rather than a classification; see
Attack types before writing logic against it.
is_test is true only for the editor's send test button, which always sends
attack_detected with fixed sample data. Check it before paging anyone.
There is no attack id
The payload carries no id, no ended_at and no duration, and the ended event
repeats the same keys as the detected one. Correlate a pair by target_ip plus
data.started_at, and use attack history when you
need the durable record — that is where ids, ended_at and duration_seconds
live.
The ended event is not immediate
attack_ended is not sent the moment traffic drops — it follows once the attack
is settled, which is why the peaks it carries are the final figures rather
than the peaks-so-far the detected payload carried.
Two consequences worth designing around:
- the "all clear" arrives after the attack actually stopped, so the gap between them is not downtime and should not be measured as any
- if the same address is under attack again before it is sent, the ended event is
suppressed entirely. A missing
attack_endedis not an error, and does not mean the first attack is still running
Carpet bombs
When a prefix you announce is under a carpet bomb, a private webhook's payload gains an aggregate block:
{
"carpet_bomb": {
"active": true,
"prefix": "192.0.2.0/24",
"ip_count": 42,
"peak_bps": 480000000000,
"peak_pps": 61000000,
"attack_types": ["UDP Flood", "TCP Flood"],
"started_at": "2026-08-19T14:02:11+00:00"
}
}It sits at the top level of a generic payload, alongside data, and is omitted
entirely when no carpet bomb is active. Discord and Slack get the same figures as
four extra fields.
Delivery
| Property | Value |
|---|---|
| Method | POST, Content-Type: application/json |
| Timeouts | 5s connect, 10s total |
| Retries | 3, backing off 30s → 60s → 120s |
| Success | any 2xx |
| On repeated failure | the last error is recorded against the webhook; nothing is disabled automatically |
A slow endpoint is the usual cause of a duplicate: if you take longer than ten
seconds to answer, the delivery is retried and you may process the same event
twice. Answer 2xx immediately and do the work asynchronously.
The URL is the only secret
There is no signature header. Anything that knows the URL can post to it, and anyone who learns it receives your attack alerts. Treat it like a credential: keep it out of shared documents and public repositories, and rotate it by replacing the webhook if it leaks.
Treat the body as unauthenticated input for the same reason. Alerting on it is fine; taking a destructive automated action on it — failing a service over, reprovisioning, changing DNS — should be confirmed against attack history first.
Community webhooks
The editor's Community Alerts toggle marks a webhook as posting into a channel your organisation does not control — a public status Discord, for example. Those deliveries are censored:
- no traffic figures at all, and no carpet-bomb data
- a sentence naming the address, and for
genericadata.messageanddata.target_iponly - attack events only. Billing and maintenance events cannot be subscribed, because they would leak commercial detail into a public room