Receive your first webhook
In this lesson you will run a small webhook receiver on your own computer, subscribe it to project changes in Drum, and watch the events arrive as you edit a project. Along the way you will check each request’s signature, and see what happens to a request that is not from Drum.
You need:
- A Drum account with a project you can edit, and the
manage_webhookspermission - Python 3 (nothing to install from pip)
cloudflared, to give your computer a public HTTPS address. Install it from Cloudflare’s downloads page. No Cloudflare account is needed
It takes about 15 minutes.
Write the receiver
Drum signs every request with a secret that belongs to the subscription. The receiver rebuilds the signature from the raw body and only trusts the request when the two match.
- Create a folder for this lesson and open a terminal in it.
-
Save this as
receiver.py:import hashlib, hmac, json, os, time from http.server import BaseHTTPRequestHandler, HTTPServer SECRET = os.environ["DRUM_WEBHOOK_SECRET"].encode() class Receiver(BaseHTTPRequestHandler): def do_POST(self): body = self.rfile.read(int(self.headers.get("Content-Length", 0))) timestamp = self.headers.get("X-Webhook-Timestamp", "") signature = self.headers.get("X-Webhook-Signature", "") expected = "sha256=" + hmac.new(SECRET, timestamp.encode() + b"." + body, hashlib.sha256).hexdigest() fresh = timestamp.isdigit() and abs(time.time() - int(timestamp)) < 300 if not (fresh and hmac.compare_digest(signature, expected)): self.send_response(401) self.end_headers() print("Rejected a request with a bad signature") return self.send_response(200) self.end_headers() event = json.loads(body) record = event["data"]["attributes"] print(f'{event["event"]}: {event["data"]["type"]} {event["data"]["id"]} "{record.get("name")}" updated_at {record.get("updated_at")}') def log_message(self, *args): pass print("Listening on http://localhost:8000") HTTPServer(("localhost", 8000), Receiver).serve_forever()
The receiver answers 200 as soon as the signature checks out, and 401 when it does not.
Open a tunnel
Drum only sends to https addresses on the public internet, so your computer needs one.
- Open a second terminal.
-
Run:
cloudflared tunnel --url http://localhost:8000 - Find the line with an address ending in
trycloudflare.com, such ashttps://quiet-river-1234.trycloudflare.com. Copy it.
Leave this terminal running. The address works only while the tunnel runs, and changes each time you start it.
Create the subscription
- In Drum, open your user menu and select Profile.
- Select Webhooks, then Create Webhook.
- Enter the Name “First webhook tutorial”.
- In Url, paste the tunnel address.
- Under Subscribed events, select
project.updated. - Select Create Webhook.
Drum opens the subscription’s page, with the status Active.
- Select the copy button next to Secret.
Start the receiver
In the first terminal, start the receiver with the secret you copied:
DRUM_WEBHOOK_SECRET='paste-the-secret-here' python3 receiver.py
You see Listening on http://localhost:8000.
Make a change in Drum
- In Drum, open a project you can edit.
- Change its name, for example by adding “ (test)” to the end, and save.
Within a few seconds the receiver prints a line like this:
project.updated: Project 12345 "Office Renovation (test)" updated_at 2026-10-13T02:14:07.412345Z
That is a signed event from Drum, checked by your own code. If saving the project changed it more than once, a second line follows about 5 seconds later.
Make several quick changes
- Change the project’s name back, and save.
- Straight away, change its end date, and save.
The receiver prints one line for the first change. About 5 seconds later it prints one more line with the project as it is after both changes. Drum combines quick changes to one record so your system is not flooded, and always sends the final state. Events explains the rule.
Send a request that is not from Drum
Anyone who knows your address can send it a request. In a third terminal, try it:
curl -i -X POST https://quiet-river-1234.trycloudflare.com \
-H "Content-Type: application/json" \
-H "X-Webhook-Timestamp: $(date +%s)" \
-H "X-Webhook-Signature: sha256=0000" \
-d '{"event":"project.updated"}'
Use your own tunnel address. The response is 401 Unauthorized, and the receiver prints Rejected a request with a bad signature. Without the secret, nobody can make a request your receiver will accept.
Clean up
- Stop the receiver and the tunnel with
Ctrl+C. - In Drum, open the subscription’s page and select Delete.
- Rename your project back if you need to.
If you leave the subscription in place with the tunnel stopped, every delivery fails, and after 10 failed deliveries in a row Drum disables the subscription and emails you.
What you learned
- A subscription sends the events you choose to one HTTPS address, signed with its own secret.
- Your endpoint checks the signature against the raw body and answers quickly with a
2xx. - Quick changes to one record arrive as fewer
.updatedevents, with the final state last.
Next, set up a real endpoint with Set up a webhook, or read How Drum webhooks work.