2 · Deposit reporting
Phase 2 of the flow. Felix sends USDC to Nexa's BitGo custody wallet. Tesser holds the keys to that wallet, observes the arrival, and reports it to Nexa together with the risk screening results from Tesser's core platform.
Step 3 · Report the USDC deposit [N]
Operation: POST /v1/quotes/{tesser_quote_id}/deposits: Report USDC deposit (op 3)
· hosted by Nexa, called by Tesser.
This is a report, not a request. Tesser attests facts: USDC arrived, in this amount, via these transactions, with these risk screening results. Nexa alone decides what to do with the report (credit, request additional funds, or reject) and that decision arrives asynchronously via webhook (phase 3).
Consequently this endpoint has no synchronous risk or amount errors. A 2xx means "report recorded", nothing more. It does not mean Nexa accepted the amount, and it does not mean the risk result was satisfactory.
Tesser always reports a deposit it observes, including when risk screening rejects. The compliance outcome travels in the payload; it never suppresses the report.
The report payload
| Field | Notes |
|---|---|
tesser_deposit_id | UUIDv4 minted by Tesser; idempotency key for this report. |
usdc_amount | What actually arrived in this report, which is not necessarily the quoted usdc_amount. |
risk_status | Tesser core-API enum, verbatim: unchecked, awaiting_decision, automatically_approved, manually_approved, automatically_rejected, manually_rejected. |
risk_status_reasons | Tesser core-API RiskStatusReason objects, verbatim. risk_status_reason_type is one of ENTITY, ACCOUNT, OWNERSHIP, COUNTERPARTY, INDIRECT, BEHAVIORAL, ERROR, plus a category and a message. |
transaction_hashes | The on-chain transaction(s). This is the field both ops teams reconcile against the chain with when something disagrees. |
Those two fields are Tesser core-API types used verbatim, so there is one compliance dictionary across both companies.
Repeatable by design
Each report is a new sub-resource, so a quote can carry several. That is
what makes the additional-funds flow work. The response's
usdc_total_reported is the sum across every report on the quote.
Late reports (a report arriving when the quote is already CREDIT_SENT,
CREDIT_CONFIRMED or PAYMENT_*, for example when Felix sends extra funds
after crediting) return 200, are recorded with no state change, appear in the
quote GET's deposits array, and
are flagged for ops review on both sides. The always-report rule has no
exception, so this case has defined success semantics. What Nexa does with
late funds is part of open question 3.
Rejection is not an undo
The USDC has already moved on-chain before this endpoint is ever called. A
later quote.rejected decision means "Nexa will not credit GTQ", not "the
deposit didn't happen". Disposition of the received USDC (return it? hold it
pending review?) is operational and out-of-band for the POC
(open question 4).
What can go wrong: report USDC deposit
error_code | HTTP | When |
|---|---|---|
VALIDATION_ERROR | 400 | Malformed amount, bad UUID, missing risk fields. |
UNAUTHENTICATED | 401 | Client certificate or customer API key missing/invalid. |
QUOTE_NOT_FOUND | 404 | No quote with this tesser_quote_id. |
QUOTE_NOT_ACCEPTED | 409 | The quote is still CREATED. Accept it first. |
QUOTE_EXPIRED | 409 | The quote reached EXPIRED before any deposit was reported. |
QUOTE_REJECTED | 409 | The quote is in REJECTED. A new quote is the recovery path. |
DEPOSIT_CONFLICT | 409 | Same tesser_deposit_id, different body. |
INTERNAL_ERROR | 500 | Unexpected failure on Nexa's side. |
There is deliberately no risk-rejection error and no amount-mismatch error here. Both are decisions, and decisions arrive by webhook.
Next: 3 · Credit decision.