Send events to other apps with webhooks
A webhook sends a short message from Rookery to an address you choose, the moment something happens. Use it to start a Zap, a Make scenario, an n8n workflow or your own code when a client replies, documents come in or someone books a time.
Webhooks come with the Pro and Team plans, with the API. Only owners and admins can set them up.
Add a webhook
- Go to Settings → Integrations → Webhooks.
- Under Send to, paste the address your other app gave you. It must start with
https://. - Add a Description, like "Zapier: new client replies", so you know what it's for later.
- Under Send these events, tick what it should hear about.
- If you have a private inbox, choose From which inboxes: Team inboxes, or Only my private inbox.
- Click Add webhook.
- Copy the Signing secret now. It starts with
whsec_, and it won't be shown again.
Then open the webhook and click Send test event to check it arrives. The answer shows straight away, and in the log.
The events
| Event | When it's sent |
|---|---|
ask.completed | Everything you asked a client for is in (document requests today) |
client.replied | Someone writes back on a conversation, by email or on a secure page |
conversation.assigned | A conversation is given to someone, or to nobody |
turn.changed | Whose turn it is changes: ours, theirs or done |
draft.approved | A person approves a helper's or an agent's draft |
message.sent | An email goes out |
booking.created | Someone books a time with you |
booking.cancelled | A booking is cancelled |
scout.held | Scout holds a likely scam |
webhook.test | You click Send test event |
What a message looks like
Each message is a POST with a small JSON body. It has ids and a one-line summary. It never has the email's words, a note, a file or an attachment. To read more, use the ids with the API.
{
"id": "evt_4pj6sy63n8spf4h9",
"type": "client.replied",
"version": 1,
"created_at": "2026-10-13T09:14:02.118Z",
"workspace": "wsp_0qadwjbmg1dw6wws",
"data": {
"conversation_id": "cnv_zys19vqj63sb1z4v",
"message_id": "msg_7h2kq9m3x1c8v5bn",
"mailbox_id": "mbx_d56zb3gp9g8qj3qt",
"secure": false,
"summary": "Dana Wu replied",
"url": "https://app.userookery.com/c/cnv_zys19vqj63sb1z4v"
}
}
idis the same on every try and on a Resend, so your app can skip one it has already handled.versionchanges only if an event changes in a way that could break your app. New fields can be added at any time.
Who hears about private mail
A webhook for Team inboxes hears about team inboxes only, never anyone's private inbox. A webhook for Only my private inbox hears about yours, and only you can see it, its log and what it sent.
Check that a message came from Rookery
Every message is signed the Standard Webhooks way, with three headers:
webhook-id: the event's idwebhook-timestamp: when it was sent, in secondswebhook-signature:v1,and a signature. Sometimes there are two, with a space between them
To check it, sign webhook-id.webhook-timestamp.body with HMAC-SHA256, using the secret after whsec_ (decoded from base64) as the key. Accept the message if your result matches any of the signatures, and refuse a timestamp more than 5 minutes old. Always use the raw body, before any JSON parsing.
The standardwebhooks library for your language does all of this for you. Without it:
Node.js
import { createHmac, timingSafeEqual } from "node:crypto";
export function fromRookery(secret, headers, rawBody) {
const id = headers["webhook-id"];
const timestamp = headers["webhook-timestamp"];
if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) return false;
const key = Buffer.from(secret.replace(/^whsec_/, ""), "base64");
const expected = createHmac("sha256", key).update(`${id}.${timestamp}.${rawBody}`).digest();
return headers["webhook-signature"].split(" ").some((part) => {
const [version, signature] = part.split(",");
const given = Buffer.from(signature ?? "", "base64");
return version === "v1" && given.length === expected.length && timingSafeEqual(given, expected);
});
}
Python
import base64, hashlib, hmac, time
def from_rookery(secret: str, headers: dict, raw_body: bytes) -> bool:
msg_id = headers["webhook-id"]
timestamp = headers["webhook-timestamp"]
if abs(time.time() - int(timestamp)) > 300:
return False
key = base64.b64decode(secret.removeprefix("whsec_"))
signed = f"{msg_id}.{timestamp}.".encode() + raw_body
expected = base64.b64encode(hmac.new(key, signed, hashlib.sha256).digest()).decode()
return any(
part.startswith("v1,") and hmac.compare_digest(part[3:], expected)
for part in headers["webhook-signature"].split(" ")
)
Change the secret
Open the webhook and click Rotate secret. Copy the new one. For the next 24 hours every message is signed with both secrets, so you can update your app without missing anything. After that, only the new one works.
Retries and the delivery log
- Any
2xxanswer counts as delivered. Answer quickly, within 10 seconds, and do the slow work afterwards. - Anything else is tried again: after about 30 seconds, 2 minutes, 10 minutes, 30 minutes, 1 hour, 3 hours, 6 hours and 12 hours. That's about a day in all.
- Rookery doesn't follow redirects. Use the final address.
- An answer of
410 Gonemeans "stop sending". Rookery turns the webhook off and emails you. - If a webhook fails for a whole day without one success, Rookery pauses it and emails you. Nothing more is sent until you click Resume.
The webhook's page has a Delivery log of the last 30 days. Filter it by Failed or Delivered, or by event. Open a row to see each try, what your app answered and exactly what was sent. Resend sends that event again, now.
You can Pause a webhook any time, and Delete it when you're done. Deleting can't be undone.
Recipes
Zapier
- Make a Zap with Webhooks by Zapier as the trigger, and choose Catch Hook.
- Copy the address Zapier shows, and add it in Rookery as a webhook.
- In Rookery, click Send test event. Back in Zapier, click Test trigger and pick the request.
- Use
typeanddata.summaryin your Zap. To act only on replies, add a Filter step:typeexactly matchesclient.replied.
Make
- Make a scenario that starts with Webhooks → Custom webhook, and click Add to make a webhook.
- Copy its address and add it in Rookery.
- Click Re-determine data structure in Make, then Send test event in Rookery.
- Add a Router with a filter on
typefor each event you handle.
n8n
- Add a Webhook node. Set HTTP Method to
POSTand Respond to "Immediately". - Copy the Production URL (it must be https) and add it in Rookery.
- Activate the workflow, then click Send test event in Rookery.
- To check signatures, add a Code node with the Node.js check above, using the raw body.
Good to know
- Rookery only sends to public
https://addresses. It won't send to private networks or to an address that points to one. - Each webhook can have its own events. Add as many as you need.
- Every change to a webhook is in the audit log, with who made it.