Customer enrichment (recommended)
Every backend can implement the customer-enrichment contract with HTTPS JSON:POST /api/v2/enrichment/requestswith the server-onlyEPODE_API_KEY, operation, purpose, and optional verified/product-owned identity references.- Rewrite the returned consent and answer actions to fixed company-owned same-origin routes.
- At those routes, forward only the
aqr1_authorization and allowlisted request body to Epode. Never forward cookies, product authentication, arbitrary headers, or the company key. POST /api/v2/customer-contextbefore personalization.- Record the exact signal IDs with
/api/v2/personalization/decisions, then link the business result with/api/v2/personalization/outcomes.
_epode and return the normal JSON or server-rendered HTML response.
The initial company request is one typed same-call event:
surface defaults to http_json and is exactly http_json, html, or mcp. Status, duration,
session, and runtime are optional. A session reference must come from a product-issued journey, and a
runtime hint must be a bounded, non-sensitive label observed by the company’s server. Never use a prompt,
transcript, model/tool argument, raw customer content, or regulated trait. Disable automatic redirect following
on every request carrying the product key and treat any 3xx as a failed enrichment call; return the unchanged
product response.
Legacy structured outcome feedback
Use the manual protocol only when a first-party SDK is unavailable. Your implementation needs standard HTTP, JSON, UUIDs, SHA-256, HMAC-SHA256, and base64url encoding.Download the contract
accountRef and userRef only from verified product authentication and
anonymousRef only from a stable, product-owned first-party pre-login ID. Keep customerRef only for durable
ask_once; when accountRef is also present it must be the exact same opaque account ID. Never pair
customerRef with userRef alone, and never use a name, email address, prompt, caller value, or agent-supplied
value. Emit sessionRef only from server/product state that already proves a journey.
Implement the response path
1
Validate configuration at startup
Require a v2
af_live_... product key. Read AGENT_FEEDBACK_MODE; never place the key in a browser.2
Select eligible responses
Include only configured product routes. Exclude errors, redirects, assets, health/metrics, streams,
binaries, and Epode’s own endpoints.
3
Create a stable Ask once subject
For
ask_once with a stable customer reference, derive
afsub1_ + base64url(HMAC-SHA256(SHA256("epode-consent-scope:" + consent_scope), "customer-ref:" + trimmed_customer_ref)),
where consent_scope is the key’s scope segment as defined in the protocol README. This keeps subjects
stable across product-key rotation. The bundled protocol/v1/README.md and conformance.json are canonical;
verify your derivation against the conformance vector before shipping.
Do not put a consent lookup on the product response path. A cold response can safely emit a
subject-bound consent_required capability. Epode Companion inspects that capability and treats Epode’s
remembered decision as authoritative before asking or reporting. You may refresh /api/v2/consent/state
in a bounded background task and cache approved or declined decisions briefly; an outage must never delay
or fail the product response.4
Sign a capability locally
Follow the compact claim order and signing vector in
protocol/v1/conformance.json. The capability
must expire within two hours. Append the derived subject as the optional s claim only for durable
Ask once, followed by r, the non-negative consent revision returned by Epode. Use 0 when the
subject is unknown. A stale revision cannot overwrite a newer decision. Never include the raw
customer reference or product content. The pseudonymous subject is
intentionally linkable within that product’s consent scope. Without a cached decision, emit the
cold consent_required stage rather than waiting on Epode.5
Add the stage-appropriate handoff
Append
_agentFeedback to JSON objects without wrapping them. Use the Agent-Feedback header for arrays,
scalars, or immutable shapes. Add Cache-Control: private, no-store. never_ask and previously approved
Ask once interactions use state: "feedback_ready". Unknown Ask once and every Ask always interaction use
state: "consent_required" with only requiredAction.submitDecision; do not expose submit or a report
schema before approval. A declined Ask once decision emits a non-requesting state: "feedback_disabled"
envelope with only a manageConsent block so an explicit user request can later change the decision.
A failed background Ask once refresh leaves the cold subject-bound handoff intact.6
Queue telemetry
Send opportunity telemetry in bounded background batches. Identity and journey references belong only in
this private telemetry event; they never enter the agent-facing envelope. Neither telemetry delivery nor
Ask once state refresh may delay the response.
HTML
For safe, finite server-rendered HTML, serialize the same complete, schema-valid envelope inside<script id="agent-feedback" type="application/json">. Escape it for an HTML script-data context. If safe
mutation is not possible, use the response header contract.
Verify
Run the signing vector, validate produced envelopes and decisions againstenvelope.schema.json and
consent-decision.schema.json, and exercise never_ask, unknown/approved/declined Ask once, and Ask always.
Simulate Epode downtime and verify that a duplicate report returns the first accepted result. See
Protocol overview.