Requests and signatures
Request
Every delivery is an HTTP POST to the subscription’s URL, with a JSON body.
| Header | Value |
|---|---|
Content-Type |
application/json |
X-Webhook-Event |
The event, such as project.created |
X-Webhook-Timestamp |
Unix time, in seconds, when this attempt was signed |
X-Webhook-Signature |
sha256= followed by the HMAC-SHA256 signature in lower-case hex |
X-Webhook-Delivery-Id |
The id of this delivery. The same on every automatic attempt |
Body
{
"event": "project.created",
"timestamp": "2026-09-10T02:14:07.412345Z",
"data": {
"id": 12345,
"type": "Project",
"attributes": {}
}
}
| Field | Meaning |
|---|---|
event |
The event, the same as X-Webhook-Event |
timestamp |
When the event was recorded, in UTC with six decimal places. A retry keeps the original value |
data.id |
The record’s id |
data.type |
The record type: Project, Invoice, Cost, Company, or for trigger.fired any type in Payloads |
data.attributes |
The record. Its fields depend on data.type. See Payloads |
company.merged and trigger.fired add fields at the top level, beside event. The body is at most 256 KB. An event whose body would be larger is not sent.
Signature
The signature is an HMAC-SHA256, in lower-case hex, of the timestamp header, a full stop, and the raw request body, keyed with the subscription’s secret:
X-Webhook-Signature = "sha256=" + hex(HMAC-SHA256(secret, X-Webhook-Timestamp + "." + raw_body))
- Use the raw body exactly as received, not re-encoded JSON.
- Compare signatures in constant time.
- Reject a request whose
X-Webhook-Timestampis more than a few minutes old.
Each attempt is signed when it is sent, so X-Webhook-Timestamp is the time of the attempt, not of the event. Code to verify a signature is in Verify webhook signatures.
The secret
| Created | With the subscription. One secret per subscription |
| Shown | On the subscription’s page in Drum, to members with manage_webhooks. In the API, only in the response that creates the subscription |
| Changed | Never. Create a new subscription to get a new secret |
Where requests come from
Drum sends from its own servers. It does not publish a fixed list of IP addresses, so check the signature instead of the sender’s address.