Core API
Webhook callbacks for signed operator decisions
Validate signed Contro1 callbacks and safely resume AI workflows after approvals, rejections, expirations, or escalations.
Webhooks are the handoff point between human review and agent execution, so signature verification and idempotency are mandatory.
Key takeaways
- Every callback is signed with HMAC-SHA256 over timestamp plus body.
- Reject callbacks older than 5 minutes to prevent replay.
- Return 200 even on idempotent duplicates so Contro1 stops retrying.
- Map approved, denied, timed_out, and cancelled outcomes to explicit branches.
- Callbacks include policy_context when the original request included policy or risk evidence.
- Enforce the approval in the executing system: bind it to the exact action, run each request_id once, pull-verify on doubt.
Why webhook verification matters
If your callback path accepts forged decisions, your approval layer is not real.
Every workflow that resumes after human review should verify timestamp and signature before applying the response.
What your handler should do
- Verify X-CentCom-Signature and X-CentCom-Timestamp
- Use X-CentCom-Request-Id for correlation in logs
- Reject stale callbacks
- Deduplicate by delivery or request ID
- Map approved, denied, timed_out, and cancelled outcomes explicitly
Enforce approvals at execution: the anti-bypass guardrail
A signed webhook is more than a callback - it is cryptographic proof of a human decision. Put the check inside the system that performs the action (the payment service, the deploy runner, the CRM writer), not inside the agent. When the executing system refuses to act without a verified approval, no agent can perform that action by skipping Contro1 - including shadow agents nobody registered.
Four rules turn the webhook into a real gate:
- Signature + freshness: reject an invalid X-CentCom-Signature and any timestamp older than 5 minutes, so an old approval cannot be replayed. The timestamp marks callback delivery, not request creation - a decision that takes hours or days still arrives freshly signed, and every retry is re-signed, so long SLAs are unaffected.
- Bind the approval to the exact action: match metadata / correlation_id and the action parameters (amount, target, record ids) before executing. "An approval arrived" is never permission for a different action.
- One-time use: execute each request_id exactly once (keep an idempotency record), so one approval cannot authorize a second run.
- Pull-verify when in doubt: confirm state directly with GET /v1/requests/:id using a read-only API key instead of trusting what an agent hands you.
Payload shape
The primary fields for new webhook consumers are request_id, status, response, responded_by, responded_at, metadata, risk_level, policy_trigger, policy_context, and approval_comment_required.
structured_response duplicates the operator response in protocol terms, and protocol_response contains the full canonical Contro1Response for SDK adapters. If you are writing a simple handler, prefer response plus status.
Verification example
Delivery states
- answered - the operator submitted a response
- callback_pending - Contro1 is attempting to deliver the signed callback
- callback_delivered - the callback URL returned a successful 2xx response
- callback_failed - retries were exhausted or delivery failed permanently
- closed - the request lifecycle is complete after successful callback delivery
Frequently asked questions
What should happen if callback delivery fails?
Your workflow should be able to recover by reading request state from the API and replaying the final decision safely.
Can I resume workflows synchronously instead of with a webhook?
You can poll in simple setups, but signed callbacks are the better pattern for long-running or multi-team production flows.
How many retries will Contro1 attempt?
Up to 5 retries with exponential backoff. Your handler should be idempotent - the same delivery ID may arrive more than once.
Do I need to respond quickly?
Return 200 within 10 seconds. If your downstream workflow is slow, acknowledge immediately and process asynchronously.
Does my server timezone affect signature verification?
No. The timestamp is Unix epoch seconds (UTC-based), which is identical everywhere in the world at the same instant - timezones are only a display layer. The reviewer timezone is irrelevant too: only the send time of the callback enters the signature. The one thing that can break verification is an actually-wrong clock: keep your server NTP-synced (cloud providers do this by default) so it stays within the 5-minute freshness window.
Does the 5-minute freshness window conflict with a long SLA?
No. The timestamp marks when the callback is sent, not when the request was created. A decision that takes hours or days still arrives freshly signed, and every retry is re-signed. The window only blocks replayed callbacks.