Human In Circuit DocsAPI v2 · Contract 2.0.0
Browse all documentation

Start

OverviewQuickstartConceptsReview playground

Build

AuthenticationAPI ReferenceCallbacksTroubleshooting

Integrations

n8nZapier

Callbacks

Receive the completed human decision and resume the originating workflow.

Add a callback to the review

{
  "title": "Approve generated article",
  "callback": {
    "url": "https://your-system.example.com/human-review-callback"
  }
}

Human In Circuit sends an HTTPS POST request after the human decision is committed.

Callback body

{
  "api_version": "v1",
  "schema_version": "review.v1",
  "callback_version": "callback.v1",
  "event_id": "54f54df3-9823-44c6-aef8-02a816941d17",
  "type": "review.resolved",
  "review_id": "d835e7dc-1f8f-4a75-a57b-0f2bd5826a1f",
  "external_id": "article-123",
  "project_key": "content-publishing",
  "agent_key": "content-agent",
  "workflow_key": "article-publishing",
  "metadata": {
    "article_id": "article-123",
    "locale": "en-IN"
  },
  "review": {
    "schema_version": "review.v1",
    "workflow_revision": 3,
    "policy_snapshot": {
      "version": "policy.v1",
      "source": "workflow_gate",
      "review_gate_revision": 5,
      "approval_behavior": "review_and_edit",
      "feedback_mode": "required",
      "undo_seconds": 6
    },
    "title": "Approve generated article",
    "summary": "An article is ready for editorial review.",
    "details_markdown": "Check the claims, tone, and final wording.",
    "priority": "normal",
    "risk": "low",
    "expires_at": null,
    "agent": {
      "name": "n8n",
      "workflow": "Publish article",
      "tags": ["n8n"]
    },
    "metadata": {
      "article_id": "article-123",
      "locale": "en-IN"
    },
    "fields": [
      {
        "key": "article",
        "label": "Article",
        "type": "textarea",
        "required": true
      }
    ],
    "initial_values": {
      "article": "Original generated article"
    },
    "attachments": []
  },
  "outcome": "approved",
  "original_values": {
    "article": "Original generated article"
  },
  "final_values": {
    "article": "Human-approved article"
  },
  "output": {
    "article": "Human-approved article"
  },
  "changes": [],
  "rejection_reasons": [],
  "note": "",
  "decided_at": "2026-07-23T10:35:00.000Z",
  "delivered_at": null
}

Important identifiers:

  • api_version: the URL-major API contract selected by the API key.

  • schema_version: the field/rendering schema pinned when the review was created.

  • callback_version: the callback body schema. Route by this before parsing future versions.

  • event_id: identifies this callback event. Deduplicate callbacks with this value.

  • review_id: identifies one review.

  • external_id: connects the review to an object in your system.

  • project_key: identifies the Human In Circuit project that owns the review.

  • metadata: the caller-owned JSON submitted with the review, returned unchanged.

  • review: the submitted review content and field definitions, excluding callback secrets.

Contract versions are immutable: api_version, schema_version, and callback_version are fixed when the review is created, so the body you parse never changes shape underneath you.

Review behavior is live. If an administrator changes the inbox while this review is waiting, the pending review picks up the new approval behavior, feedback rules, undo window, and rejection reasons, and policy_snapshot reflects the updated values. Once a review has been decided its snapshot is frozen, so the callback always reports the policy the decision was actually made under.

Decision data:

  • outcome: approved or rejected.
  • original_values: the values originally submitted.
  • final_values: the values after human edits.
  • output: a developer-friendly alias of final_values; use this to continue the workflow.
  • changes: JSON Patch operations describing edits.
  • rejection_reasons and note: human feedback when rejected.

Why are final_values and output empty?

The callback can only return values that were submitted for review. Send the starting object as initial_values (canonical) or output (accepted input alias). When neither contains values, a decision-only review still works, but original_values, final_values, and output are {}. Each editable value should normally use the same key as its entry in fields.

Fixed secret header

A fixed secret header is the simplest callback authentication option for n8n.

Add it to the review:

{
  "callback": {
    "url": "https://n8n.example.com/webhook-waiting/...",
    "auth_header": "x-human-in-circuit-token",
    "auth_value": "replace-with-a-long-random-secret"
  }
}

Human In Circuit adds this header to the callback:

x-human-in-circuit-token: replace-with-a-long-random-secret

In n8n, configure the Wait node to use a Header Auth credential with the same header name and value. Store the secret in n8n credentials rather than exposing it in a shared workflow.

auth_value is encrypted before it is stored by Human In Circuit.

HMAC signatures for custom applications

Every callback is also signed. When an API key is generated, copy its callback signing secret; it is shown only once.

Callbacks include:

X-Hic-Event-Id: <event-id>
X-Hic-Timestamp: <unix-seconds>
X-Hic-Signature: v1=<hex-hmac-sha256>

Each header is also sent under its former name — X-Loopr-Event-Id, X-Loopr-Timestamp, X-Loopr-Signature — with identical values. Existing integrations keep working unchanged; new ones should verify against the X-Hic- names, and the X-Loopr- names will be removed in a future release.

To verify a callback:

  1. Read the raw request body without reformatting it.
  2. Build <timestamp>.<raw-json-body>.
  3. Compute HMAC-SHA256 with the callback signing secret.
  4. Compare it with the value after v1= using a timing-safe comparison.
  5. Reject old timestamps; five minutes is a reasonable tolerance.
  6. Deduplicate successful processing by event_id.

Delivery and retries

Callback delivery is at least once. There are three total automatic attempts:

  1. immediately;
  2. one minute after the first failure;
  3. five minutes after the second failure.

A request succeeds only when the destination returns an HTTP 2xx status within 15 seconds. Redirects are not followed.

After automatic retries end:

  • inspect the event under Organization admin → Health;
  • use Retry to send the same event again; or
  • retrieve the stored result with GET /v1/reviews/{review_id}.

The ten most recent callback events are shown newest-first under Health.

Resending a final review

Retry and Resend are different actions, and neither is time limited:

  • Retry re-delivers the event unchanged, keeping its original event_id. A receiver that deduplicates on event_id will recognise and ignore a delivery it already handled. Use it when a delivery failed.
  • Resend sends the same final review under a new event_id, with the previous one recorded as replay_of. Use it when the downstream workflow must run again — including when the original delivery already succeeded. Resend is also available on the review itself, under the decision summary.

Because Resend deliberately defeats event_id deduplication, it can run the receiving workflow a second time. Both actions require the callbacks.manage capability and are recorded as callback.replayed review events.

Polling fallback

Method

GET

URL

https://humanincircuit.com/v1/reviews/<REVIEW_ID>

Header

Authorization: Bearer <YOUR_API_KEY>

Reviews and decisions are stored in the database indefinitely by default. Polling remains available after callback retries have finished.

Callback safety rules

Callback URLs must:

  • use HTTPS;
  • match the configured hostname restriction when one is set;
  • resolve to a public network address;
  • contain no embedded username or password;
  • return directly without a redirect.