Guide to Featurebase Webhooks: Features and Best Practices
Featurebase webhooks tell your systems when something changes in your feedback and support workspace: a customer posts a feature request, a request changes status, someone upvotes, an Update is published, or a conversation gets a reply. Featurebase combines feedback boards, roadmaps, changelogs and a support inbox, and webhooks keep your own tools in sync with it without polling the API.
This guide covers how Featurebase webhooks are registered and signed, the topics and envelope, the retry and pause rules, and the practices that keep a Featurebase receiver reliable.
What are Featurebase webhooks?
A Featurebase webhook is an HTTPS URL subscribed to a list of topics. Webhooks are available on paid plans (Growth and higher), and a workspace can have up to 10. Each event arrives as a POST with a JSON envelope that names the topic and carries the changed object in data.item.
Every request has two headers: X-Webhook-Signature, a hex HMAC-SHA256 of <timestamp>.<raw body>, and X-Webhook-Timestamp, the Unix time in seconds. The signing secret starts with whsec_, but this isn't Standard Webhooks. The whole string, prefix included, is the HMAC key, and there are no webhook-id or webhook-signature headers.
Featurebase webhook features
| Feature | Details |
|---|---|
| Plans | Growth and higher |
| Configuration | Settings > Developers > Webhooks, or POST /v2/webhooks |
| Limit | 10 webhooks per workspace |
| Signature header | X-Webhook-Signature, hex |
| Timestamp header | X-Webhook-Timestamp, Unix seconds |
| Scheme | HMAC-SHA256 |
| Signed content | <timestamp>.<raw body> |
| Secret | whsec_..., used whole as the key. Rotatable; the old secret stops at once |
| Response window | 5 seconds by default. The API can set up to 30 seconds |
| Success codes | 200, 201, 202 or 204 only |
| Retries | Up to 3: 1 minute, 1 hour and 6 hours after each failed attempt |
| Pause and disable | Paused after 100 failures in a row. Disabled after 500 failures, or 3 pauses, in 24 hours |
| Custom headers | Through the API |
| Source IPs | Not published |
Common events
Featurebase documents 27 topics:
| Area | Topics |
|---|---|
| Requests (posts) | post.created, post.updated, post.deleted, post.voted |
| Tickets | ticket.created, ticket.updated, ticket.deleted |
| Comments | comment.created, comment.updated, comment.deleted |
| Updates | changelog.published |
| Conversations | conversation.user.created, conversation.admin.assigned, conversation.admin.closed, conversation.admin.opened, conversation.admin.snoozed, conversation.admin.unsnoozed, conversation.priority.updated, conversation.handover_, conversation.contact.attached, conversation.contact.detached, conversation.read, conversation.deleted |
| Messages | conversation.user.replied, conversation.admin.replied, conversation.admin.noted, conversation_ |
Featurebase calls feature requests "Requests" in the product and post in the API, and calls changelogs "Updates". For post.voted, data.item is a post_vote with action (add or remove), postId and voter. For conversation topics, data.item is the conversation without its messages. For message topics, it's a single message, and data.changes never appears.
Every event uses the same envelope. Here is Featurebase's post.updated example:
{
"object": "notification_event",
"id": "notif_0193a70c-3015-757c-a260-22ae37c86608",
"topic": "post.updated",
"organizationId": "6595518396205e06b897ad65",
"webhookId": "675346db13af7340748ce850",
"createdAt": "2026-10-09T12:00:00.000Z",
"data": {
"object": "notification_event_data",
"item": {
"object": "post",
"id": "67546dfb6e1363426b90707f",
"title": "Dark mode for the dashboard",
"content": "<p>Please add a dark theme.</p>",
"boardId": "6755d0970b5d5b1fefdf54f4",
"postUrl": "https://feedback.example.com/p/dark-mode-for-the-dashboard",
"createdAt": "2026-10-01T09:30:00.000Z",
"updatedAt": "2026-10-09T12:00:00.000Z"
},
"changes": [
{ "field": "title", "oldValue": "Dark mode", "newValue": "Dark mode for the dashboard" }
]
}
}
id is a unique event ID that starts with notif_. object is always notification_event, so route on topic, not object. data.item follows your workspace's default API version, set in Settings > Developers > API, and nothing in the delivery says which version that is. Conversation events also carry conversationId and conversationUrl.
Setting up Featurebase webhooks
- Build an endpoint on a public
https://URL that acceptsPOST, keeps the raw body, and returns200,201,202or204within 5 seconds. - In Featurebase, open Settings > Developers > Webhooks, add the URL and select topics.
- Copy the signing secret, which starts with
whsec_, and store it asFEATUREBASE_.WEBHOOK_ SECRET
Creating the webhook through the API (POST /v2/webhooks) also lets you add custom headers and raise the timeout to 30 seconds.
With Hookdeck. Featurebase is a supported source type in Hookdeck. Create a source with the Featurebase type and paste the whole secret, including the whsec_ prefix. Featurebase uses it as a plain HMAC key. Use the Hookdeck source URL as the webhook URL in Featurebase. The source accepts POST only and checks X-Webhook-Signature against <X-Webhook-Timestamp>.<raw body>. A request that fails the check still gets a 200 from Hookdeck, but Hookdeck marks it as failed verification and doesn't deliver it to your service.
Securing Featurebase webhooks
Join the timestamp header, a literal ., and the raw body bytes. Compute an HMAC-SHA256 keyed with the whole secret as a UTF-8 string, and compare the hex digests in constant time. Don't strip whsec_, don't base64-decode the secret, and don't use a Standard Webhooks library.
const crypto = require('crypto');
const express = require('express');
function verifyFeaturebaseSignature(rawBody, signature, timestamp, secret) {
if (!signature || !timestamp || !secret) return false;
if (!/^[0-9a-f]{64}$/i.test(signature)) return false; // validate before decoding
// The key is the whole whsec_... string.
const signed = Buffer.concat([Buffer.from(`${timestamp}.`, 'latin1'), rawBody]);
const expected = crypto.createHmac('sha256', secret).update(signed).digest();
const received = Buffer.from(signature, 'hex');
return received.length === expected.length && crypto.timingSafeEqual(received, expected);
}
// Optional replay check, off by default (0). Hookdeck retries and replays resend the original timestamp.
const TOLERANCE_SECONDS = Number(process.env.FEATUREBASE_WEBHOOK_TOLERANCE_SECONDS || 0);
const app = express();
app.post('/webhooks/featurebase', express.raw({ type: '*/*' }), (req, res) => {
const secret = process.env.FEATUREBASE_WEBHOOK_SECRET;
if (!secret) return res.sendStatus(500); // fail closed
const timestamp = req.get('x-webhook-timestamp');
if (!verifyFeaturebaseSignature(req.body, req.get('x-webhook-signature'), timestamp, secret)) {
return res.sendStatus(401);
}
if (TOLERANCE_SECONDS > 0 && Math.abs(Date.now() / 1000 - Number(timestamp)) > TOLERANCE_SECONDS) {
return res.sendStatus(401);
}
const event = JSON.parse(req.body.toString('utf8'));
// Deduplicate on event.id, route on event.topic, then process asynchronously.
res.sendStatus(200);
});
The same check in Python:
import hashlib
import hmac
import re
HEX_64 = re.compile(r"[0-9a-fA-F]{64}")
def verify_featurebase_signature(raw_body: bytes, signature, timestamp, secret: str) -> bool:
if not signature or not timestamp or not secret:
return False
if not HEX_64.fullmatch(signature):
return False
signed = timestamp.encode("latin-1") + b"." + raw_body
expected = hmac.new(secret.encode("utf-8"), signed, hashlib.sha256).digest()
return hmac.compare_digest(bytes.fromhex(signature), expected)
Featurebase's own samples reject any request whose timestamp is more than 5 minutes old. Hookdeck applies no timestamp tolerance, and its retries and replays resend the original headers, so a delivery Hookdeck retries after a 5-minute outage would fail that check. Behind Hookdeck, leave the window off or make it wider than your retry schedule, and rely on deduplicating by id.
Make Featurebase webhooks production-ready. Hookdeck Event Gateway verifies the
X-Webhook-Signatureheader with your wholewhsec_secret, stores every request, ticket and conversation event, and delivers it to your services with retries you control.
Featurebase webhook limitations and pain points
A whsec_ secret that isn't Standard Webhooks
The Problem: Verification fails on every delivery with the correct secret.
Why It Happens: The whsec_ prefix and the X-Webhook-* header names suggest Standard Webhooks, whose libraries strip the prefix, base64-decode the rest and expect a webhook-id header and a base64 signature. Featurebase uses the whole string as the key and sends a hex digest.
Workarounds:
- Use a plain HMAC-SHA256 keyed with the full secret, as above.
How Hookdeck Can Help: Hookdeck's Featurebase source uses the secret exactly as pasted, so there's nothing to strip or decode.
Events are dropped while a webhook is paused
The Problem: After an outage, some events never arrive, even once your endpoint is back.
Why It Happens: Featurebase retries a failed delivery only three times. After 100 failures in a row it pauses the webhook, starting at 5 minutes and doubling up to 4 hours, and after 500 failures or 3 pauses in 24 hours it disables it. Featurebase says it "does not keep events while a webhook is paused or disabled".
Workarounds:
- Watch for paused or disabled webhooks and re-enable them in settings or through the API.
- After an outage, use the API to fetch anything you missed.
How Hookdeck Can Help: Hookdeck accepts each delivery as soon as it arrives, so your outages don't count as Featurebase failures, then delivers to your service with automatic retries and lets you replay stored events.
A strict 5-second window and a short list of success codes
The Problem: Deliveries count as failures even though your handler worked.
Why It Happens: Featurebase accepts only 200, 201, 202 or 204, within 5 seconds by default. A slow handler, a redirect or another 2xx code such as 203 fails.
Workarounds:
- Return
200or204straight after verification and do the work in the background. - Raise the timeout, up to 30 seconds, when you create the webhook through the API.
How Hookdeck Can Help: Hookdeck answers Featurebase immediately, and your service can take as long as it needs behind it.
Rotation stops the old secret at once
The Problem: Events fail verification between a rotation and your next deploy.
Why It Happens: Featurebase says the old secret "stops working at once" when you click Rotate secret.
Workarounds:
- Deploy the new secret immediately after rotating, and accept both secrets in your verifier during the changeover.
How Hookdeck Can Help: With Hookdeck in front, you update the secret in one place, on the source.
Best practices
Verify against the raw body
Featurebase tells you to verify before you parse, because re-serialising the JSON changes the bytes. Use express.raw() in Express, await request.arrayBuffer() in a Next.js route handler, and await request.body() in FastAPI.
Deduplicate on id
The same event can arrive more than once. Store each notif_... ID and skip ones you've already handled. See our guide to webhook idempotency.
Don't rely on order
Events can arrive out of order. Use createdAt, or fetch the latest state from the API, before overwriting data.
Fetch conversation messages from the API
Conversation topics carry the conversation without its messages. Use conversationId to fetch them when you need the thread.
Acknowledge fast and process asynchronously
Return a 2xx before slow work. See why to process webhooks asynchronously.
Conclusion
Featurebase signs each event with X-Webhook-Signature, a hex HMAC-SHA256 of <X-Webhook-Timestamp>.<raw body>, keyed with the whole whsec_ secret. Verify with a plain HMAC rather than a Standard Webhooks library, deduplicate on the notif_ ID, answer within 5 seconds with one of the four accepted codes, and keep any timestamp check optional so retried events still pass.
Hookdeck Event Gateway sits in front of your receiver, checks every Featurebase signature, and holds each event until your service has processed it, so an outage on your side doesn't pause the webhook or lose events.
Get started with Hookdeck for free and handle Featurebase webhooks reliably in minutes.