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:approvedorrejected.original_values: the values originally submitted.final_values: the values after human edits.output: a developer-friendly alias offinal_values; use this to continue the workflow.changes: JSON Patch operations describing edits.rejection_reasonsandnote: 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:
- Read the raw request body without reformatting it.
- Build
<timestamp>.<raw-json-body>. - Compute HMAC-SHA256 with the callback signing secret.
- Compare it with the value after
v1=using a timing-safe comparison. - Reject old timestamps; five minutes is a reasonable tolerance.
- Deduplicate successful processing by
event_id.
Delivery and retries
Callback delivery is at least once. There are three total automatic attempts:
- immediately;
- one minute after the first failure;
- 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 onevent_idwill 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 asreplay_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.