# Quick start

Felix Pago sells USDC to Banco Nexa for GTQ. **Tesser orchestrates**: it
quotes with Nexa, reports the USDC arriving at Nexa's BitGo custody, and
instructs the GTQ payout. Felix and Nexa never talk to each other.

<Callout type="note" title="Scope of this integration: one fixed beneficiary, no last mile">

Nexa always pays **the same beneficiary**, agreed out-of-band and never
present in an API payload. That beneficiary is the **payout partner**, which
performs the last-mile payout to the individual remittance receivers.

**Nexa is not involved in the last mile.** There are no
per-recipient beneficiary details anywhere in this contract, no destination
objects and no beneficiary compliance fields, because every payout goes to
the same place. Nexa taking on last-mile remittance payouts is **a later stage of the
programme**, out of scope here.

</Callout>

## The parties

| Party | Role in this integration |
| --- | --- |
| **Banco Nexa** | Quotes the rate, buys the USDC, credits the GTQ and executes the payout. Hosts the quote, deposit and payment API. |
| **Tesser** | Orchestrator. Mints every quote, watches Nexa's BitGo custody wallet, reports deposits with risk screening, instructs the payout. Hosts the webhook receiver Nexa calls, plus the core API Nexa uses for treasury. |
| **Felix Pago** | The remitter buying GTQ. Interacts through Tesser's Slack bot at launch. Felix never calls Nexa. |

## All ten operations

Throughout the portal, <N /> means **Nexa hosts it, Tesser calls it** and
<T /> means **Tesser hosts it, Nexa calls it**. Direction below is
**caller → receiver**.

| # | Operation | Direction | Purpose |
| --- | --- | --- | --- |
| 1 | [`POST /v1/quotes`](/api-nexa/quotes#create-quote-op-1) <N /> | T→N | Tesser requests a GTQ quote for a customer |
| 2 | [`POST /v1/quotes/{tesser_quote_id}/accept`](/api-nexa/quotes#accept-quote-op-2) <N /> | T→N | Lock the quote |
| 3 | [`POST /v1/quotes/{tesser_quote_id}/deposits`](/api-nexa/deposits#report-usdc-deposit-op-3) <N /> | T→N | Tesser reports USDC received at custody (+ risk screening) |
| 4 | [`POST`/`HEAD /v1/webhooks/nexa/{organizationId}`](/api-tesser/webhooks#receive-nexa-webhook-op-4) <T /> | N→T | Nexa → Tesser events: credit decisions, payout failures |
| 5 | [`POST /v1/payments`](/api-nexa/payments#create-payment-op-5) <N /> | T→N | Instruct GTQ payout to the fixed beneficiary |
| 6 | [`GET /v1/accounts/{id}`](/api-tesser/accounts#get-an-account-by-id) <T /> | N→T | Existing core API: Nexa's USDC balance |
| 7 | [`POST /v1/treasury/withdrawals`](/api-tesser/treasury#create-withdrawal) <T /> | N→T | Existing core API: withdraw USDC |
| 8 | [`GET /v1/quotes/{tesser_quote_id}`](/api-nexa/quotes#get-quote-optional-op-8) <N /> *(optional)* | T→N | Reconciliation / state recovery |
| 9 | [`GET /v1/payments/{nexa_payment_id}`](/api-nexa/payments#get-payment-optional-op-9) <N /> *(optional)* | T→N | Reconciliation / state recovery |
| 10 | [`POST /v1/webhooks/tesser`](/api-nexa/webhooks#receive-tesser-webhook-nexa-implements-this) <N /> | T→N | Tesser → Nexa events: withdrawal status (supports op 7) |

"Optional" on 8 and 9 refers to Nexa's implementation obligation only. Both
are fully specified, stubbed and tested here either way.

## The four connections

Those ten operations ride on just **four connections**. Two parties, two
kinds of traffic (**API calls** and **webhooks**), each going both ways.
That is the whole integration.

<Mermaid
  chart={`flowchart LR
    T["Tesser"]
    N["Banco Nexa"]
    T -- "API calls" --> N
    T -- "webhooks" --> N
    N -- "API calls" --> T
    N -- "webhooks" --> T`}
/>

Get those four working, one call each, and everything else is more of the
same.

## Build them in this order

Each milestone is **one working call**, end to end, in the real security
model. Later milestones reuse the credentials the earlier ones prove out, so
the order is not arbitrary.

| # | Connection | Direction | The one call that proves it |
| --- | --- | --- | --- |
| 1 | **API call**, Tesser to Nexa, over mTLS | T→N | [Create quote](/api-nexa/quotes#create-quote-op-1) <N /> returns `201` |
| 2 | **Webhook**, Nexa to Tesser | N→T | [Receive Nexa webhook](/api-tesser/webhooks#receive-nexa-webhook-op-4) <T /> returns `2xx` |
| 3 | **API call**, Nexa to Tesser | N→T | [Get an account by ID](/api-tesser/accounts#get-an-account-by-id) <T /> returns `200` |
| 4 | **Webhook**, Tesser to Nexa | T→N | [Receive Tesser webhook](/api-nexa/webhooks#receive-tesser-webhook-nexa-implements-this) <N /> returns `2xx` |

### 1 · mTLS, then create a quote <N />

**The first milestone is the certificate exchange, not the endpoint.**
Everything else in this integration is blocked on it, so do it first and
prove it with the simplest call in the contract.

- **Nexa** stands up [`POST /v1/quotes`](/api-nexa/quotes#create-quote-op-1)
  behind mTLS, and decides how to validate Tesser's client certificate:
  pin the leaf fingerprint, or validate against a Tesser-provided issuing
  CA. Nexa also issues Felix's Nexa API key.
- **Tesser** presents that client certificate **plus**
  `Authorization: Bearer <felix_api_key>` on every call.

**Done when:** one `POST /v1/quotes` returns `201` with a price,
`quoted_gtq` and `usdc_amount` over a mutually authenticated connection.

Details: [Authentication](/authentication) ·
[1 · Quote & accept](/flow-nexa/quote-accept)

### 2 · Nexa sends a webhook to Tesser <T />

Every Nexa decision in this flow is asynchronous, so this leg carries more
of the integration than any other single piece.

- **Nexa** registers its client certificate's SHA-256 fingerprint against
  its Tesser API key during onboarding, then delivers to
  [`POST /v1/webhooks/nexa/{organizationId}`](/api-tesser/webhooks#receive-nexa-webhook-op-4).
- **Check connectivity first** with
  [`HEAD /v1/webhooks/nexa/{organizationId}`](/api-tesser/webhooks#verify-webhook-connectivity-op-4)
  <T />, which uses the same path and the same auth, with no body and no
  event consumed.

**Done when:** one real event is delivered and acked `2xx`.
`quote.credit.sent` is the natural choice.

Details: [Webhooks guide](/webhooks-guide) ·
[3 · Credit decision](/flow-nexa/credit-decision)

### 3 · Nexa reads its USDC balance <T />

This is an **existing, live** Tesser core-API endpoint, and it reuses
exactly the certificate milestone 2 registered, so it is the cheapest
possible confirmation that Nexa's outbound credentials work for API calls
and not just webhook posts.

- **Nexa** calls
  [`GET /v1/accounts/{id}`](/api-tesser/accounts#get-an-account-by-id) with
  the wallet account ID shared during onboarding.

**Done when:** one `200` with the account's USDC balance.

Details: [5 · Treasury](/flow-nexa/treasury)

### 4 · Tesser sends a webhook to Nexa <N />

Last, because it is the only leg with new Tesser platform work behind it:
Tesser's webhook sender signs with Ed25519 today and is being extended to
present a client certificate for Nexa deliveries.

- **Nexa** stands up
  [`POST /v1/webhooks/tesser`](/api-nexa/webhooks#receive-tesser-webhook-nexa-implements-this)
  and subscribes to withdrawal status events.

**Done when:** one withdrawal-status event is delivered and acked `2xx`.

Details: [Authentication](/authentication) ·
[Webhooks guide](/webhooks-guide)

### Then: the rest of the flow

With the four connections proven, every remaining operation is one of those
same two patterns, so the work is the flow rather than the plumbing. Pick
them up at [accept quote](/api-nexa/quotes#accept-quote-op-2) <N /> and walk
them in order on the [flow pages](/flow-nexa/lifecycle). Before you
write any of it, read the four
[contract rules](/flow-nexa/lifecycle#contract-rules). They decide most of
this integration's edge cases.

## Where everything lives

| | |
| --- | --- |
| [Flow: Nexa's perspective](/flow-nexa/lifecycle) | Scope, glossary, the state machine, then one page per phase with diagrams and failure modes |
| [Authentication](/authentication) | mTLS both directions, certificate exchange and rotation |
| [Webhooks guide](/webhooks-guide) | Envelope, event catalogue, dedupe, ordering rules |
| [End-to-end test](/e2e-test) | One script that walks the whole flow and checks every step |
| [Testing & stubs](/testing-stubs) | The stub host and the magic error id |
| [Open questions](/open-questions) | Ten items, the agenda for the technical meeting |
| [Nexa endpoints exposed to Tesser](/api-nexa) | API reference for everything Nexa hosts |
| [Tesser endpoints exposed to Nexa](/api-tesser) | API reference for everything Tesser hosts |

Read [Open questions](/open-questions) before milestone 1. Several of them
(the quote validity window and Nexa's certificate-validation choice) need
an answer before the first call can succeed.
