Read revision 2All revisions

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 words
  • removed words
13 unchanged passages

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.

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.*

*Written in October 2026. Polling with get_feedback is on every plan; webhooks are a Pro feature.*

Option 1: ask with get_feedback

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.

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.

> 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

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.

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:

Useful options:

• project: every document in a project, instead of one document.

• project: every document in a project, instead of one document.

• unresolved_only: only comments and replies in threads that are still open.

• 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.

• 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

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.

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)

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 when you create it.

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:

Events:

33 unchanged passages

Event · When

Event · When

--- · ---

--- · ---

comment.created · A reader leaves a comment

comment.created · A reader leaves a comment

comment.replied · Someone replies in a thread

comment.replied · Someone replies in a thread

comment.resolved · A thread is resolved

comment.resolved · A thread is resolved

document.review_requested · Review is requested on a document

document.review_requested · Review is requested on a document

document.approved · A document is approved

document.approved · A document is approved

Endpoints can be limited to chosen projects.

Endpoints can be limited to chosen projects.

How deliveries behave

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.

• 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.

• 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.

• 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.

• 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.

• Delivery log. Each attempt is logged, so you can see what was sent and what came back.

Verify the signature

Verify the signature

Each request carries these headers:

Each request carries these headers:

• Offprint-Event and Offprint-Event-Id: the event type and a unique id (use it to ignore duplicates)

• Offprint-Event and Offprint-Event-Id: the event type and a unique id (use it to ignore duplicates)

• Offprint-Timestamp: Unix time in seconds

• Offprint-Timestamp: Unix time in seconds

• Offprint-Signature: v1= followed by an HMAC-SHA256 of {timestamp}.{raw body}, using your signing secret

• Offprint-Signature: v1= followed by an HMAC-SHA256 of {timestamp}.{raw body}, using your signing secret

```js
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))
  );
}
```
```js
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.

Verify against the raw request body, before any JSON parsing, or the signature won't match.

Use both

Use both

Because webhooks are sent once and never retried, the sturdy pattern is:

Because webhooks are sent once and never retried, the sturdy pattern is:

1. A webhook arrives and wakes the agent.

1. A webhook arrives and wakes the agent.

2. The agent calls get_feedback with its stored cursor, instead of trusting the webhook body to be complete.

2. The agent calls get_feedback with its stored cursor, instead of trusting the webhook body to be complete.

3. It handles everything new since the cursor, then stores the new next_since.

3. 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.

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

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.

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

Questions

Do I need webhooks? No. Polling with get_feedback covers most setups, and it's free.

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.

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.

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.