Gareth Wilson Gareth Wilson

Guide to Featurebase Webhooks: Features and Best Practices

Published


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

FeatureDetails
PlansGrowth and higher
ConfigurationSettings > Developers > Webhooks, or POST /v2/webhooks
Limit10 webhooks per workspace
Signature headerX-Webhook-Signature, hex
Timestamp headerX-Webhook-Timestamp, Unix seconds
SchemeHMAC-SHA256
Signed content<timestamp>.<raw body>
Secretwhsec_..., used whole as the key. Rotatable; the old secret stops at once
Response window5 seconds by default. The API can set up to 30 seconds
Success codes200, 201, 202 or 204 only
RetriesUp to 3: 1 minute, 1 hour and 6 hours after each failed attempt
Pause and disablePaused after 100 failures in a row. Disabled after 500 failures, or 3 pauses, in 24 hours
Custom headersThrough the API
Source IPsNot published

Common events

Featurebase documents 27 topics:

AreaTopics
Requests (posts)post.created, post.updated, post.deleted, post.voted
Ticketsticket.created, ticket.updated, ticket.deleted
Commentscomment.created, comment.updated, comment.deleted
Updateschangelog.published
Conversationsconversation.user.created, conversation.admin.assigned, conversation.admin.closed, conversation.admin.opened, conversation.admin.snoozed, conversation.admin.unsnoozed, conversation.priority.updated, conversation.handover_requested, conversation.contact.attached, conversation.contact.detached, conversation.read, conversation.deleted
Messagesconversation.user.replied, conversation.admin.replied, conversation.admin.noted, conversation_part.redacted

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

  1. Build an endpoint on a public https:// URL that accepts POST, keeps the raw body, and returns 200, 201, 202 or 204 within 5 seconds.
  2. In Featurebase, open Settings > Developers > Webhooks, add the URL and select topics.
  3. Copy the signing secret, which starts with whsec_, and store it as FEATUREBASE_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-Signature header with your whole whsec_ 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 200 or 204 straight 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.


Gareth Wilson

Gareth Wilson

Product Marketing

Multi-time founding marketer, Gareth is PMM at Hookdeck and author of the newsletter, Community Inc.