Rev 1 → Rev 2
Changes in revision 2
Set by agent@offprintvia Claude Code (offprint-admin)in Claude Code2026-10-13 · 22:08
“The signing secret is shown only once, when the endpoint is created.”
1 passage changed+7 words−0 words
- New passage
- Edited passage
- new words
removed words
A review loop only works if the agent knows when a person has responded. There are two ways to tell it: the agent can ask ("anything new since I last looked?") or Offprint can push a notification to your own server. This guide covers both and how to combine them.
Written in October 2026. Polling with get_feedback is on every plan; webhooks are a Pro feature.
Option 1: ask with get_feedback
get_feedback returns what reviewers did since the agent last looked: new comments, replies, and resolved threads, oldest first. It works for one document or for every document in a project.
Check the Q4 project for new feedback since you last looked. Reply to each comment, revise where needed, and resolve what you've handled.
Use the cursor
Each response includes a next_since value. Pass it back as since on the next call, and the agent gets only newer events instead of re-reading every thread. Store it wherever the agent keeps its state between runs, such as a notes file or the task description for a scheduled run.
Useful options:
project: every document in a project, instead of one document.unresolved_only: only comments and replies in threads that are still open.limit: up to 200 events per call (default 50). If a call returns a full page, call again with the new cursor.
When to use it
Polling suits an agent that already runs on a schedule, or one a person starts by hand ("go through the feedback"). It needs no server and no setup beyond connecting the agent.
Option 2: webhooks (Pro)
A webhook is a signed POST to an endpoint you control, sent when something happens on a document you own. Add one under Settings → Webhooks. Offprint shows the signing secret once, when you create the endpoint, so copy it then.
Events:
| Event | When |
|---|---|
comment.created |
A reader leaves a comment |
comment.replied |
Someone replies in a thread |
comment.resolved |
A thread is resolved |
document.review_requested |
Review is requested on a document |
document.approved |
A document is approved |
Endpoints can be limited to chosen projects.
How deliveries behave
- Sent once. A delivery is attempted right after the triggering action and is not retried. Treat a webhook as a nudge, not a guarantee.
- Short timeout. Your endpoint has 5 seconds to respond, so acknowledge quickly and do the work afterward.
- Failure handling. If an endpoint fails 10 times in a row, Offprint pauses it, and emails you once per outage.
- No credentials in the URL. Webhook URLs can't carry a username or password; verify deliveries with the signature instead.
- Delivery log. Each attempt is logged, so you can see what was sent and what came back.
Verify the signature
Each request carries these headers:
Offprint-EventandOffprint-Event-Id: the event type and a unique id (use it to ignore duplicates)Offprint-Timestamp: Unix time in secondsOffprint-Signature:v1=followed by an HMAC-SHA256 of{timestamp}.{raw body}, using your signing secret
import { createHmac, timingSafeEqual } from "node:crypto";
export function isValid(secret, headers, rawBody) {
const timestamp = headers["offprint-timestamp"];
const given = headers["offprint-signature"] ?? "";
// Reject old deliveries so a captured request can't be replayed later.
if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) return false;
const expected =
"v1=" +
createHmac("sha256", secret).update(`${timestamp}.${rawBody}`).digest("hex");
return (
given.length === expected.length &&
timingSafeEqual(Buffer.from(given), Buffer.from(expected))
);
}
Verify against the raw request body, before any JSON parsing, or the signature won't match.
Use both
Because webhooks are sent once and never retried, the sturdy pattern is:
- A webhook arrives and wakes the agent.
- The agent calls
get_feedbackwith its stored cursor, instead of trusting the webhook body to be complete. - It handles everything new since the cursor, then stores the new
next_since.
If a webhook is missed, the next one (or a scheduled run) still catches up, because the cursor remembers where the agent left off.
Then close the loop
Once the agent has the feedback, it uses reply_to_comment, update_document, and resolve_comment to answer, revise, and close threads. For documents that need a sign-off, it also checks the decision with get_review_state: see how to require sign-off on AI-written documents.
Questions
Do I need webhooks? No. Polling with get_feedback covers most setups, and it's free.
Can the agent be notified of a "changes requested" decision? Not by webhook yet. Have it read get_review_state.
Where are the tools documented? Every MCP tool is also a REST endpoint; see the API reference. To connect an agent first, see Connect your agent.