Panel actions
Add your own buttons to feedback threads — links that open your tools, or signed webhooks that run from the dashboard.
Panel actions are up to five custom buttons a project adds to every feedback item: “Open in Linear”, “Send to agent”, “View in admin”. You define them in the dashboard on the project's Actions tab (Panel actions card, Add action). Adding, editing and removing actions — and revealing or rotating the signing secret — is for organisation owners and admins only; members see the tab read-only.
| Kind | Where it shows | What happens |
|---|---|---|
| Link | The widget's thread card (if visible to everyone) and the dashboard | Opens the URL in a new tab, with placeholders filled in. |
| Webhook | The dashboard only | The server POSTs the feedback to your URL, signed, and shows the result. |
Fields
Prop
Type
Link placeholders
| Placeholder | Replaced with |
|---|---|
{id} | The feedback's id |
{number} | Its number in the project (142) |
{url} | The page URL, without the ?pinwren= deep-link parameter |
{projectSlug} | The project's slug |
Every value is URL-encoded, so a page URL can never break out of the query parameter or path segment it sits
in, and the result must still be an http(s) URL — anything else isn't rendered. Unknown placeholders are
rejected when you save.
https://linear.app/acme/new?title=Feedback%20%23{number}&description={url}
https://admin.example.com/feedback/{projectSlug}/{id}Links open with rel="noopener noreferrer". Only link actions visible to everyone are ever sent to the
page: team links and webhook URLs never leave the server.
Who sees what
| Who | Actions tab (settings) | Feedback item in the dashboard |
|---|---|---|
| Owners and admins | Every action, with its URL; can edit | Every action; webhook buttons run, their URL is hidden |
| Members | Every action, read-only; webhook URLs hidden | Every action; can run webhook actions |
| Viewers | Read-only; webhook and team URLs hidden | Only actions visible to everyone; nothing runs |
A webhook action's URL is never sent to the inbox, even for admins — the server looks it up when the button is pressed.
Webhook actions
Running a webhook action from the dashboard makes the server POST this JSON to your URL, with a 10-second
timeout and without following redirects. Like webhooks, the
URL must resolve to a public address: private and internal targets fail with
blocked: the address is private or reserved unless the instance operator lists them in
PINWREN_OUTBOUND_ALLOWLIST.
{
"id": "5d1f0c9e-…",
"event": "action.run",
"apiVersion": "2026-11-01",
"createdAt": "2026-10-22T09:30:00.000Z",
"data": {
"feedback": {
"id": "…",
"number": 142,
"projectId": "…",
"projectName": "Marketing site",
"kind": "bug",
"status": "open",
"priority": "high",
"title": "Checkout button does nothing",
"body": "Clicking Pay shows a spinner forever.",
"pageUrl": "https://example.com/checkout",
"reporterName": "Sam",
"assigneeIds": [],
"createdAt": "2026-10-22T09:12:00.000Z",
"updatedAt": "2026-10-22T09:12:00.000Z",
"url": "https://feedback.example.com/…"
},
"action": { "id": "send-to-agent", "label": "Send to agent" },
"actor": { "kind": "user", "id": "…" }
}
}It carries the same headers as generic webhooks — Pinwren-Event,
Pinwren-Delivery, Pinwren-Timestamp and Pinwren-Signature: v1=<hex HMAC-SHA256(secret, timestamp + "." + body)>
— signed with the project's signing secret (whsec_…), which organisation owners and admins can reveal and
rotate in the Signing secret card on the same Actions tab.
Answer with any 2xx to mark the run as successful. A JSON body like { "message": "Queued for the agent" }
is shown to the team member as a toast (up to 200 characters) — only on a 2xx answer. Any other answer shows just
its status (HTTP 500, or 301 for a redirect), and a failed connection shows a category such as timed out;
the body of an error response is never shown. Every run is logged in the item's activity timeline.
import { createHmac, timingSafeEqual } from "node:crypto";
export function verify(secret: string, timestamp: string, body: string, header: string) {
const expected = `v1=${createHmac("sha256", secret).update(`${timestamp}.${body}`).digest("hex")}`;
const a = Buffer.from(expected);
const b = Buffer.from(header);
return a.length === b.length && timingSafeEqual(a, b);
}Verify against the raw request body, and reject timestamps that are more than a few minutes old.
Agent webhook (“Fix with agent”)
Turn on Agent webhook when you add a webhook action to offer it under Fix with agent on a feedback item
instead of as a plain button. Running it POSTs the usual signed action.run payload with one extra field,
data.brief:
{
"event": "action.run",
"data": {
"feedback": { "…": "…" },
"action": { "id": "claude-bot", "label": "Claude bot" },
"brief": {
"markdown": "# Fix feedback #142: …",
"hints": { "route": "/cart", "files": [] },
"links": { "dashboard": "https://…" }
}
}
}The brief is built from the diagnostics already stored and masked at capture. It never contains the session replay
or the screenshot — it links to the item in the dashboard instead — so nothing visual reaches the receiver (or any
AI) unless a person shares it. The receiver can start work straight from brief.markdown, or call back through the
MCP server with get_repro_test for a draft Playwright test.