Project webhooks

Send new feedback to your agent.

Each project can send signed feedback.created events to one HTTPS endpoint. Delivery is asynchronous and uses a durable retry queue.

Receiver safety

Feedback content and submitter email are customer data. Protect them in transit, in logs, and at rest.

At-least-once delivery

Store each delivery ID before processing. A retry uses the same ID, so your receiver can ignore duplicates.

Payload

Optional values are present as null. Attachments are represented only by attachmentCount.

feedback.created
{
  "id": "event_cuid",
  "type": "feedback.created",
  "createdAt": "2026-08-18T12:34:56.000Z",
  "project": { "id": "project_cuid", "name": "Example" },
  "feedback": {
    "id": "feedback_cuid",
    "content": "The save button does not work.",
    "email": "person@example.com",
    "status": "OPEN",
    "category": "BUG",
    "sentiment": "NEGATIVE",
    "summary": "The save action fails.",
    "priority": 82,
    "pageUrl": "https://example.com/settings",
    "browser": "Chrome",
    "os": "Windows",
    "device": "desktop",
    "attachmentCount": 1,
    "createdAt": "2026-08-18T12:34:55.000Z"
  },
  "analysis": { "status": "complete" }
}

Headers and signature

Sign the exact raw body. Do not parse and serialize it before verification.

Node.js receiver
import { createHmac, timingSafeEqual } from "node:crypto";

const timestamp = request.headers.get("x-feedbackbasket-timestamp");
const received = request.headers.get("x-feedbackbasket-signature");
const rawBody = await request.text();
const expected = "v1=" + createHmac("sha256", process.env.WEBHOOK_SECRET)
  .update(timestamp + "." + rawBody, "utf8")
  .digest("hex");

const valid = received !== null &&
  Buffer.byteLength(received) === Buffer.byteLength(expected) &&
  timingSafeEqual(Buffer.from(received), Buffer.from(expected));
X-FeedbackBasket-Eventfeedback.created or webhook.test
X-FeedbackBasket-DeliveryStable delivery ID
X-FeedbackBasket-TimestampUnix seconds
X-FeedbackBasket-Signaturev1=<lowercase HMAC hex>

Delivery rules

  • Requests stop after 10 seconds and never follow redirects.
  • Private, local, link-local, multicast, and unspecified targets are blocked.
  • HTTP 408, 429, and 5xx responses retry up to five total attempts.
  • Retries wait about 1, 5, 25, and 125 minutes. Valid 429 Retry-After values are capped at 24 hours.
  • HTTP 410 stops retries and pauses the endpoint.
  • A rotated secret takes effect immediately. Update the receiver before you send more events.