Feedback — customer action and outcome
After an assessment, your backend can report what happened when its active credential has the required capabilities. Feedback uses the independently authenticated S2S path and commits a fact, immutable receipt and audit together. It never executes an action in your product.
| Route | Capability | Fact |
|---|---|---|
POST /v1/assessments/{assessment_id}/customer-actions |
action:write |
The action your product actually executed |
POST /v1/assessments/{assessment_id}/outcomes |
outcome:write |
Later observed truth/evidence, or a correction of the current outcome |
These are separate from assessment:write: request them with the permission
Avaliações, ações e resultados when you create the server credential in the console, and
confirm with FingerTrackID that they are active. A published contract or an assessment
credential alone does not enable feedback.
Exact public contracts
Section titled “Exact public contracts”- Feedback OpenAPI
- Customer action request
- Customer action receipt
- Customer outcome request
- Customer outcome receipt
- Normative customer-action HMAC vector
- Lossless feedback canonicalization vectors
Retain assessment.v1.yaml beside the feedback OpenAPI for its shared references.
Historical implementation annotations and the example server are not issued runtime settings.
Validate the closed schemas and the normative raw-input/cross-field rules; a JSON Schema
alone does not reject duplicate decoded keys, NUL or every lossless-number violation.
Signing the feedback call
Section titled “Signing the feedback call”Use the same reference signer. The canonical path includes the exact
original synchronous assessment_id, with no query/trailing slash. The sixth signed line is
assessment_id:<id> and X-FTID-Assessment-Id must match the path. Do not send the assessment
route’s external-event header instead.
secret comes only from your backend’s secret manager. Request bodies are at most 16384 bytes;
responses are at most 65536 bytes. Keep the exact original bytes/idempotency key across retries
and create a new UUIDv7 request ID/timestamp/signature for every attempt.
// Validate once, then preserve these exact bytes for the logical feedback operation.const body = Buffer.from(JSON.stringify(customerActionRequest), 'utf8')const path = `/v1/assessments/${assessmentId}/customer-actions`// Each transport attempt receives a new request ID, timestamp and signature.const attempt = prepareAttempt({ secret, keyId, path, body })const result = await postAttempt(apiOrigin, attempt, 2000)// Validate the exact receipt or feedback Problem before accepting the result.Customer action (customer-action.v1)
Section titled “Customer action (customer-action.v1)”All seven fields are required: schema_version, external_action_id, idempotency_key,
action, basis, occurred_at and reason_code.
- Actions:
ALLOWED,CHALLENGED,HELD,REVIEWED,BLOCKED,CANCELLED. - Basis:
ASSESSMENT,CLIENT_POLICY,FALLBACK,MANUAL_REVIEW. - The reason must match its basis in the schema.
ASSESSMENTrequiresFOLLOWED_FTID_RECOMMENDATIONand an action aligned with the stored recommendation. occurred_atis your backend’s actual action time, not a browser-selected timestamp. It cannot precede the original event or exceed the server’s allowed future window.
FingerTrackID derives recommendation_alignment; never include it in the request.
CANCELLED is NOT_COMPARABLE. Matching a recommendation does not prove causality or fraud.
The receipt acknowledges the recorded action, not a newly executed block.
{ "schema_version": "customer-action.v1", "external_action_id": "action:synthetic-001", "idempotency_key": "synthetic-action-001", "action": "ALLOWED", "basis": "ASSESSMENT", "occurred_at": "2026-09-05T12:00:00.123456789+03:00", "reason_code": "FOLLOWED_FTID_RECOMMENDATION"}await ftid?.recordCustomerAction(assessmentId, { action: 'ALLOWED', basis: 'CLIENT_POLICY', reasonCode: 'CLIENT_POLICY_RULE',})The request body is a published canonicalization vector.
Outcome (customer-outcome.v1)
Section titled “Outcome (customer-outcome.v1)”Every field, including nullable fields, is required:
schema_version, external_outcome_id, idempotency_key, truth_status, label_maturity,
fraud_type, evidence_source, evidence_reference, evidence_confidence, observed_at,
reason_code and corrects_external_outcome_id.
Truth is PENDING, INSUFFICIENT_EVIDENCE, LEGITIMATE or FRAUD_CONFIRMED.
Maturity is PROVISIONAL or MATURE; PENDING must remain provisional.
Conclusive truth requires the complete evidence triple, and FRAUD_CONFIRMED additionally
requires the active fraud-type catalog entry. Never put PII or secret material in an evidence reference.
The initial observation has INITIAL_OBSERVATION and a null predecessor. A correction must
reference the current leaf’s external_outcome_id, use a new outcome ID and the reason matching
the actual change. A stale predecessor, competing successor or second root is a conflict.
Corrections append facts; they do not overwrite the original record or recommendation.
Submitted maturity/evidence does not itself qualify the label for detector calibration.
Confirmed-fraud outcomes also feed the console item Contas vizinhas com fraude confirmada pela
empresa (neighbouring accounts with fraud confirmed by your company) in
Contexto do acesso; without outcomes it shows Sem
dado: a empresa não envia resultados.
{ "schema_version": "customer-outcome.v1", "external_outcome_id": "outcome:synthetic-001", "idempotency_key": "synthetic-outcome-001", "truth_status": "PENDING", "label_maturity": "PROVISIONAL", "fraud_type": null, "evidence_source": null, "evidence_reference": null, "evidence_confidence": null, "observed_at": "2026-09-05T12:00Z", "reason_code": "INITIAL_OBSERVATION", "corrects_external_outcome_id": null}await ftid?.recordOutcome(assessmentId, { truthStatus: 'PENDING', labelMaturity: 'PROVISIONAL', reasonCode: 'INITIAL_OBSERVATION',})Receipts, retry and unavailable results
Section titled “Receipts, retry and unavailable results”201 acknowledges a new committed fact; 200 acknowledges the same logical fact without a
second effect. Divergent reuse returns 409. A timeout/lost acknowledgement is indeterminate:
reconcile using the same POST/body with a fresh signed attempt, subject to the actual Problem’s
retryable field and bounded backoff. 429/503 use Retry-After.
The feedback Problem catalog belongs to the feedback OpenAPI; do not assume every assessment
Problem is a feedback response. Target absence and a target outside the authenticated scope
share 404. No unauthenticated receipt lookup or assessment GET is provided.