Browse the docs

Webhooks

Get a signed HTTP request when reports and feature requests are created or change status.

Webhooks are on the Pro plan. Each project can send events to one HTTPS endpoint: your own server, or a Zapier, Make or n8n hook. Set it in the project's settings, under Capture, then use Send test event to check it.

Events

EventSent when
report.createdA report arrives from the widget or the API.
report.status_changedA report moves to a new status, from the dashboard, the API, the MCP server, or a synced Linear or GitHub issue.
comment.createdSomeone comments on a report.
feature_request.createdSomeone suggests a feature, in the widget, the dashboard or the API.
feature_request.status_changedA feature request moves to a new status.
webhook.testYou press Send test event. It carries an example report.

Payloads

Every event is a JSON POST with the same envelope:

{
  "id": "5d7c0a4e-…",
  "event": "report.status_changed",
  "occurred_at": "2026-10-18T14:03:22.104Z",
  "project": { "id": "1f0a…", "name": "Acme storefront" },
  "report": {
    "id": "c3d1…",
    "title": "Checkout button does nothing",
    "status": "Done",
    "previous_status": "In Review",
    "severity": "High",
    "area": "Checkout",
    "description": "Clicked Pay, nothing happened",
    "page_url": "https://shop.example.com/checkout",
    "reporter": { "name": "Ana", "email": "ana@example.com" },
    "created_at": "2026-10-17T09:12:44Z",
    "dashboard_url": "https://feedbackdots.com/reports/c3d1…",
    "issues": { "linear": "https://linear.app/…", "jira": null, "github": null }
  }
}

previous_status is only present on *.status_changed events. comment.created adds a comment object with id, author_name, body and created_at.

Feature request events carry a feature_request object instead of report:

{
  "id": "0b8e…",
  "event": "feature_request.status_changed",
  "occurred_at": "2026-10-18T14:05:00.000Z",
  "project": { "id": "1f0a…", "name": "Acme storefront" },
  "feature_request": {
    "id": "8b7e…",
    "title": "Dark mode",
    "description": "Our team works late",
    "status": "shipped",
    "previous_status": "in_progress",
    "votes": 42,
    "source": "widget",
    "author": { "name": "Sam", "email": null },
    "page_url": "https://app.example.com/settings",
    "created_at": "2026-10-12T09:30:00Z",
    "dashboard_url": "https://feedbackdots.com/feedback"
  }
}

Fields may be added to payloads over time, so ignore ones you don't use.

Headers

HeaderValue
X-FeedbackDots-EventThe event name.
X-FeedbackDots-DeliveryA unique ID for this delivery, the same as the body's id. Use it to ignore duplicates.
X-FeedbackDots-TimestampUnix seconds when the request was signed.
X-FeedbackDots-Signaturesha256= followed by the hex HMAC-SHA256 of timestamp.body, keyed with the project's signing secret.

Verifying the signature

Find the signing secret (whsec_...) under the project's webhook settings. Compute the HMAC over the timestamp, a dot, and the raw request body, exactly as received, and compare it with the header in constant time. Reject requests whose timestamp is more than five minutes old.

With the SDKs this is one call; see SDKs. By hand in Node.js:

import { createHmac, timingSafeEqual } from 'node:crypto';

export function verify(secret, rawBody, headers) {
  const timestamp = headers['x-feedbackdots-timestamp'];
  const signature = headers['x-feedbackdots-signature'] || '';
  if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) return false;
  const expected = 'sha256=' + createHmac('sha256', secret).update(`${timestamp}.${rawBody}`).digest('hex');
  return (
    expected.length === signature.length && timingSafeEqual(Buffer.from(expected), Buffer.from(signature))
  );
}

In Python:

import hashlib, hmac, time

def verify(secret, raw_body, headers):
    timestamp = headers["X-FeedbackDots-Timestamp"]
    if abs(time.time() - int(timestamp)) > 300:
        return False
    expected = "sha256=" + hmac.new(secret.encode(), f"{timestamp}.{raw_body}".encode(), hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, headers["X-FeedbackDots-Signature"])

Delivery

  • Respond with any 2xx status within 5 seconds. Do slow work after responding.
  • Redirects are not followed, and endpoints must be public HTTPS addresses.
  • Each event is sent once. The settings page shows the result of the last delivery, so you can spot a failing endpoint.
  • Rotating the secret in settings takes effect for the next event.