Skip to content
Live in production, in shadow mode: FingerTrackID recommends, your system decides.Status →

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.

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.

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.

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. ASSESSMENT requires FOLLOWED_FTID_RECOMMENDATION and an action aligned with the stored recommendation.
  • occurred_at is 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.

customer-action.v1
{
"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"
}

The request body is a published canonicalization vector.

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.

customer-outcome.v1
{
"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
}

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.