Webhooks
Webhooks let FeedbackJar notify your own systems in real time — sync new posts to Slack, open a Linear issue when a post’s status changes, or trigger a Zapier/Make workflow — without polling the REST API.
Set up webhooks under Settings → Webhooks in your project.
Events
| Event | Fired when |
|---|---|
post.created | A new post is submitted to any board |
post.status_changed | A manager changes a post’s status (e.g. OPEN → IN_PROGRESS) |
comment.created | A public comment is added to a post. Internal/pinned notes are never sent. |
member.added | Someone joins the project as a portal member |
member.removed | A manager removes a member from the project |
tag.created | A new tag is created |
tag.deleted | A tag is deleted |
board.created | A new board is created |
board.deleted | A board is deleted |
Bulk operations — like the Canny importer — never trigger these events. Importing thousands of historical posts, comments, boards, tags, or authors will not flood your webhook endpoint; only actions a person takes in the live product fire events.
Payload
Every delivery is a POST request with a JSON body shaped like this:
{
"event": "post.created",
"createdAt": "2026-07-04T10:00:00.000Z",
"data": {
"postId": "cm1abc123",
"title": "Dark mode support",
"status": "OPEN",
"boardId": "cm1board1"
}
} The data shape depends on the event — post.status_changed includes previousStatus/newStatus, comment.created includes commentId, postId, content, and authorName, member.added/member.removed include userId and role, tag.created/tag.deleted include tagId and name, and board.created/board.deleted include boardId, name, and slug.
Headers
| Header | Description |
|---|---|
Content-Type | Always application/json |
X-Feedbackjar-Event | The event name, e.g. post.created |
X-Feedbackjar-Signature | Only present if signature verification is enabled (see below) |
Signature verification
When you create a webhook, you can toggle Require signature verification (on by default). If enabled, you’ll be shown a signing secret once (whsec_...) — save it, it can’t be retrieved again, but you can generate a new one at any time from the webhook’s settings.
Every request then includes an X-Feedbackjar-Signature header formatted like Stripe’s:
t=1735689600,v1=5257a869e7bfe... t— the Unix timestamp (seconds) the request was signed atv1— an HMAC-SHA256 hex digest of{t}.{raw request body}, keyed with your signing secret
To verify a request, recompute the same HMAC and compare it to v1. Reject requests where the timestamp is more than 5 minutes old, to guard against replay.
Node.js
import crypto from "node:crypto";
function verify(secret, signatureHeader, rawBody) {
const parts = Object.fromEntries(
signatureHeader.split(",").map((p) => p.split("="))
);
const { t, v1 } = parts;
if (Math.abs(Date.now() / 1000 - Number(t)) > 300) return false;
const expected = crypto
.createHmac("sha256", secret)
.update(`${t}.${rawBody}`)
.digest("hex");
return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(v1));
} Python
import hashlib
import hmac
import time
def verify(secret: str, signature_header: str, raw_body: str) -> bool:
parts = dict(p.split("=") for p in signature_header.split(","))
t, v1 = parts["t"], parts["v1"]
if abs(time.time() - int(t)) > 300:
return False
expected = hmac.new(
secret.encode(), f"{t}.{raw_body}".encode(), hashlib.sha256
).hexdigest()
return hmac.compare_digest(expected, v1) If you’d rather skip verification (e.g. sending to an internal tool on a private network you already trust), turn the toggle off when creating the webhook — the X-Feedbackjar-Signature header is simply omitted.
Retries
If your endpoint doesn’t respond with a 2xx status (or times out after 10 seconds), the delivery is retried with exponential backoff, up to 6 attempts spread over roughly 30 minutes. After 20 consecutive failed deliveries, the webhook is automatically disabled — you can re-enable it from the settings page once your endpoint is healthy again.
Testing
Use the Send test event button next to any webhook to fire a one-off webhook.test event immediately, without waiting for a real post or comment. The response status (or error) is shown right away.
Security
Webhook URLs must use https:// and cannot point at localhost, private/internal IP ranges, or cloud metadata endpoints — this is enforced both when you add a webhook and on every delivery.