From 542dc6921bfffb4f77bc1ac3737d181d1d4d7998 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1rio=20Alvial?= Date: Tue, 14 Apr 2026 12:59:09 -0300 Subject: [PATCH 1/8] style: rebrand site with Trace Finance identity and palm theme - Replace Mintlify logos with official Trace Finance brand SVGs - Update favicon to Trace Finance geometric icon - Switch theme from mint to palm (fintech-specific) - Add gradient background decoration and Inter fonts - Set dark mode as default, breadcrumb eyebrows, tokyo-night code blocks - Add "Get started" CTA button to navbar - Redesign landing page: rename to Home, generic description, card navigation - Simplify sandbox-note snippet - Centralize URLs as Mintlify variables (sandboxUrl, productionUrl, authUrl) - Add .nvmrc for Node 22 LTS (Mintlify CLI requirement) --- .nvmrc | 1 + docs.json | 49 +++++++++++++++++++++++++++++++++------ favicon.svg | 7 +++--- index.mdx | 43 +++++++++++++++++++++++++++++++--- logo/dark.svg | 27 +++++++++++---------- logo/light.svg | 27 +++++++++++---------- snippets/sandbox-note.mdx | 3 +-- 7 files changed, 117 insertions(+), 40 deletions(-) create mode 100644 .nvmrc diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 0000000..2bd5a0a --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +22 diff --git a/docs.json b/docs.json index 1b9f874..5614b77 100644 --- a/docs.json +++ b/docs.json @@ -1,21 +1,45 @@ { "$schema": "https://mintlify.com/docs.json", - "theme": "mint", - "name": "Trace Finance Developer Docs", + "theme": "palm", + "name": "Trace Finance", "colors": { "primary": "#15803D", - "light": "#07C983", - "dark": "#15803D" + "light": "#18E299", + "dark": "#0C8C5E" }, "favicon": "/favicon.svg", "logo": { "light": "/logo/light.svg", - "dark": "/logo/dark.svg" + "dark": "/logo/dark.svg", + "href": "https://tracefinance.io" + }, + "appearance": { + "default": "dark" + }, + "background": { + "decoration": "gradient" + }, + "fonts": { + "heading": { + "family": "Inter", + "weight": 600 + }, + "body": { + "family": "Inter", + "weight": 400 + } + }, + "styling": { + "eyebrows": "breadcrumbs", + "codeblocks": { + "theme": "tokyo-night" + } }, "navigation": { "tabs": [ { "tab": "Guides", + "icon": "book-open", "groups": [ { "group": "Getting started", @@ -32,6 +56,7 @@ "guides/principles/versioning", "guides/principles/idempotency", "guides/principles/pagination", + "guides/principles/filtering", "guides/principles/money", "guides/principles/errors" ] @@ -72,7 +97,12 @@ "label": "Support", "href": "mailto:support@tracefinance.io" } - ] + ], + "primary": { + "type": "button", + "label": "Get started", + "href": "/quickstart" + } }, "api": { "baseUrl": [ @@ -83,7 +113,12 @@ "method": "bearer" } }, - "description": "API documentation for Trace Finance's multi-currency account and payment platform.", + "description": "Explore guides, examples, and API references to build with Trace Finance.", + "variables": { + "sandboxUrl": "https://faas.sandbox.tracefinance.io", + "productionUrl": "https://faas.tracefinance.io", + "authUrl": "https://auth.tracefinance.io" + }, "seo": { "indexing": "navigable" }, diff --git a/favicon.svg b/favicon.svg index d50ceed..77bc037 100644 --- a/favicon.svg +++ b/favicon.svg @@ -1,5 +1,4 @@ - - - - + + + diff --git a/index.mdx b/index.mdx index 6672c29..0bb0fc2 100644 --- a/index.mdx +++ b/index.mdx @@ -1,6 +1,43 @@ --- -title: "Trace Finance Developer Docs" -description: "Build multi-currency account and payment experiences with the Trace FX API." +title: "Home" +sidebarTitle: "Home" +description: "Explore guides, examples, and API references to build with Trace Finance." +mode: "wide" --- -{/* TODO: Write landing page content — what Trace FX is, who it's for, quick navigation cards */} +## Get started + + + + Set up your sandbox and make your first API call in minutes. + + + Learn how to authenticate requests with bearer tokens. + + + Understand sandbox and production environments. + + + +## Build with Trace + + + + Create and manage Brazilian Real accounts for your users. + + + Set up cryptocurrency accounts with full compliance. + + + Accept deposits via PIX, wire transfer, and crypto networks. + + + Process withdrawals to bank accounts and crypto wallets. + + + Exchange between BRL, USD, and crypto assets. + + + Real-time notifications for account and payment events. + + diff --git a/logo/dark.svg b/logo/dark.svg index 54a338a..f029ed6 100644 --- a/logo/dark.svg +++ b/logo/dark.svg @@ -1,13 +1,16 @@ - - - - - - - - - - - - + + + + + + + + + + + + + + + diff --git a/logo/light.svg b/logo/light.svg index 75fca1c..b6aceee 100644 --- a/logo/light.svg +++ b/logo/light.svg @@ -1,13 +1,16 @@ - - - - - - - - - - - - + + + + + + + + + + + + + + + diff --git a/snippets/sandbox-note.mdx b/snippets/sandbox-note.mdx index bb193e9..12d5c5a 100644 --- a/snippets/sandbox-note.mdx +++ b/snippets/sandbox-note.mdx @@ -1,5 +1,4 @@ - This guide uses the **sandbox** environment. Replace `faas.sandbox.tracefinance.io` - with `faas.tracefinance.io` when you move to production. + This guide uses the **sandbox** environment. See [Environments](/guides/environments) for details. From c6e0c17221e827068245095017e29cb1d4be085e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1rio=20Alvial?= Date: Tue, 14 Apr 2026 12:59:21 -0300 Subject: [PATCH 2/8] docs: write API guides, principles, and webhook content Fill all stub pages with real content adapted from legacy docs: - Authentication: Auth0 JWT flow, token lifecycle - Environments: sandbox/production URLs, rate limits, 99.8% SLA - Versioning: X-Trace-Version header, breaking vs non-breaking changes - Idempotency: X-Idempotency-Key header, 409 conflict behavior - Pagination: cursor-based with meta object (previousCursor, nextCursor) - Money: minor units with asset field, ISO 8601 dates - Errors: code/message/details structure, HTTP status codes, retry guidance - Filtering: LHS Brackets syntax with operators and logical combinators (new page) - Webhooks overview: setup, HMAC-SHA256 signature verification, retry policy - Event reference: account, payment, and beneficiary event catalog All URLs use {{variables}} from docs.json for single-source-of-truth. --- guides/authentication.mdx | 67 +++++++++++++++++- guides/environments.mdx | 39 ++++++++++- guides/principles/errors.mdx | 87 +++++++++++++++++++++++- guides/principles/filtering.mdx | 83 +++++++++++++++++++++++ guides/principles/idempotency.mdx | 70 ++++++++++++++++++- guides/principles/money.mdx | 82 ++++++++++++++++++++++- guides/principles/pagination.mdx | 78 ++++++++++++++++++++- guides/principles/versioning.mdx | 66 +++++++++++++++++- webhooks/events.mdx | 56 ++++++++++++++-- webhooks/overview.mdx | 108 +++++++++++++++++++++++++++++- 10 files changed, 723 insertions(+), 13 deletions(-) create mode 100644 guides/principles/filtering.mdx diff --git a/guides/authentication.mdx b/guides/authentication.mdx index 52c4e61..b7dfdde 100644 --- a/guides/authentication.mdx +++ b/guides/authentication.mdx @@ -4,4 +4,69 @@ description: "Obtain and use Auth0 JWT tokens to authenticate requests to the Tr sidebarTitle: "Authentication" --- -{/* TODO: Write auth guide — Auth0 flow, obtaining tokens, X-Customer-Id claim, token rotation */} +## Overview + +The Trace FX API uses [Auth0](https://auth0.com/) for authentication and authorization. Every request must include a valid JSON Web Token (JWT) in the `Authorization` header. + +During onboarding you receive a **client ID** and **client secret**. Use these to obtain an access token through the OAuth 2.0 client credentials flow. + +## Details + +### Obtaining a token + +Request an access token from the Auth0 token endpoint: + +```bash +curl --request POST \ + --url {{authUrl}}/oauth/token \ + --header 'Content-Type: application/json' \ + --data '{ + "client_id": "YOUR_CLIENT_ID", + "client_secret": "YOUR_CLIENT_SECRET", + "audience": "{{productionUrl}}", + "grant_type": "client_credentials" + }' +``` + +A successful response includes the token and its lifetime: + +```json +{ + "access_token": "eyJhbGciOiJSUzI1NiIs...", + "token_type": "Bearer", + "expires_in": 86400 +} +``` + + + In sandbox, set the `audience` to `{{sandboxUrl}}`. + + +### Using the token + +Include the token in the `Authorization` header of every request: + +```bash +curl --request GET \ + --url {{sandboxUrl}}/api/v1/accounts \ + --header 'Authorization: Bearer eyJhbGciOiJSUzI1NiIs...' +``` + +The token contains your customer identity — no separate customer ID header is needed. + +### Token lifecycle + +Tokens are valid for **24 hours** (86,400 seconds). Follow these best practices: + +1. **Store securely** — keep the token in memory after obtaining it. +2. **Check before use** — inspect the `exp` claim in the JWT payload to confirm it has not expired. +3. **Rotate proactively** — request a new token before the current one expires rather than waiting for a `401` response. + + + Do not request a new token for every API call. Reuse tokens until they expire. + + +## Related + +- [Environments](/guides/environments) — sandbox and production base URLs +- [Quickstart](/quickstart) — make your first API call diff --git a/guides/environments.mdx b/guides/environments.mdx index 65c4433..2fbeb8f 100644 --- a/guides/environments.mdx +++ b/guides/environments.mdx @@ -3,4 +3,41 @@ title: "Environments" description: "Sandbox and production base URLs, and what differs between them." --- -{/* TODO: Write environments guide — sandbox vs production URLs, what resets, rate limits */} +## Overview + +Trace FX provides two environments. Use sandbox for development and testing, and production for live integrations. + +| Environment | Base URL | +|---|---| +| Sandbox | `{{sandboxUrl}}` | +| Production | `{{productionUrl}}` | + +## Details + +### Sandbox + +The sandbox is an isolated environment that replicates production behavior with test data. Use it to build and validate your integration before going live. + +- No real money is moved. +- Test credentials are provided during onboarding. +- Data may be reset periodically. + +### Production + +The production environment processes real transactions and serves live users. Access is granted after your integration has been validated in sandbox. + +- All operations affect real accounts and funds. +- Subject to full compliance and security requirements. + +### Rate limits + +API requests are subject to rate limits in both environments. See [Errors](/guides/principles/errors) for status codes and retry guidance. + +### Availability + +The platform maintains **99.8% uptime** with the exception of scheduled maintenance or unforeseeable events. Scheduled maintenance is communicated at least **21 business days** in advance through official channels. + +## Related + +- [Authentication](/guides/authentication) — how to obtain tokens for each environment +- [Quickstart](/quickstart) — make your first sandbox API call diff --git a/guides/principles/errors.mdx b/guides/principles/errors.mdx index 264067f..7cab2a1 100644 --- a/guides/principles/errors.mdx +++ b/guides/principles/errors.mdx @@ -3,4 +3,89 @@ title: "Errors" description: "How errors are structured and how to handle them." --- -{/* TODO: Write errors guide — ErrorResponse shape (code, message, details), HTTP status codes, retry guidance */} +## Overview + +The Trace FX API uses standard HTTP status codes and returns structured error responses. Every response includes an `X-Request-Id` header you can reference when contacting support. + +## How it works + +### Error response structure + +All errors follow the same shape: + +```json +{ + "code": "INVALID_DATA", + "message": "The field 'amount.value' must be a positive integer.", + "details": {} +} +``` + +| Field | Type | Description | +|---|---|---| +| `code` | string | Machine-readable error code for programmatic handling | +| `message` | string | Human-readable description of what went wrong | +| `details` | object | Additional context (may be empty) | + +### HTTP status codes + +| Code | Meaning | When it happens | +|---|---|---| +| `200` | OK | Request succeeded | +| `201` | Created | Resource was created | +| `204` | No content | Request succeeded with no response body | +| `400` | Bad request | Invalid or malformed request data | +| `401` | Unauthorized | Missing or invalid authentication token | +| `404` | Not found | Resource does not exist | +| `408` | Request timeout | Request took too long to process | +| `409` | Conflict | Idempotency key conflict or state conflict | +| `422` | Unprocessable entity | Valid syntax but business rule violation | +| `429` | Too many requests | Rate limit exceeded | +| `500` | Internal server error | Unexpected server failure | + +### Common error codes + +| Code | HTTP status | Description | +|---|---|---| +| `INVALID_DATA` | 400 | Request body failed validation | +| `RESOURCE_NOT_FOUND` | 404 | The requested resource does not exist | +| `IDEMPOTENT_ID_CONFLICT` | 409 | Idempotency key was already used | +| `INVALID_STATUS_CHANGE` | 422 | The resource cannot transition to the requested state | +| `MFA_NOT_ENABLED` | 422 | Multi-factor authentication is required but not configured | + +### Retry guidance + +| Status code | Should retry? | Strategy | +|---|---|---| +| `400`, `401`, `404`, `422` | No | Fix the request before retrying | +| `408`, `429` | Yes | Back off and retry after the indicated period | +| `409` | Depends | Check if the original request succeeded | +| `500` | Yes | Retry with exponential backoff | + + + Include the `X-Request-Id` from the response when opening a support ticket. This helps the team trace your request through the system. + + +## Examples + +A validation error: + +```json +{ + "code": "INVALID_DATA", + "message": "The field 'amount.value' must be a positive integer.", + "details": { + "field": "amount.value" + } +} +``` + +A resource not found: + +```json +{ + "code": "RESOURCE_NOT_FOUND", + "message": "Account with id 'abc-123' was not found.", + "details": {} +} +``` diff --git a/guides/principles/filtering.mdx b/guides/principles/filtering.mdx new file mode 100644 index 0000000..29bd646 --- /dev/null +++ b/guides/principles/filtering.mdx @@ -0,0 +1,83 @@ +--- +title: "Filtering" +description: "Refine list results using the filters query parameter." +--- + +## Overview + +List endpoints support a `filters` query parameter that lets you narrow results by field values, ranges, and patterns using LHS Brackets syntax. + +## How it works + +### Syntax + +Filters follow the format `field[operator]=value`: + +``` +?filters=field[operator]=value +``` + +Combine multiple filters with a semicolon (`;`). Group conditions with logical operators `and(...)` and `or(...)`, separating conditions inside with commas. + +### Operators + +| Operator | Description | Example | +|---|---|---| +| `eq` | Equals | `id[eq]=abc-123` | +| `gt` | Greater than | `amount.value[gt]=1000` | +| `gte` | Greater than or equal | `createdAt[gte]=2025-01-01T00:00:00Z` | +| `lt` | Less than | `amount.value[lt]=50000` | +| `lte` | Less than or equal | `createdAt[lte]=2025-12-31T23:59:59Z` | +| `in` | Matches any value in list (pipe-separated) | `type[in]=CHECKING\|SAVING` | +| `nin` | Excludes values in list | `status[nin]=CLOSED\|SUSPENDED` | +| `like` | Pattern matching | `name[like]=Trace%` | +| `last` | Last element of array equals (strings) | `states.status[last]=ACTIVE` | +| `last_ne` | Last element of array not equals (strings) | `states.status[last_ne]=CLOSED` | + +### Logical operators + +Combine conditions using `and(...)` or `or(...)`: + +``` +?filters=and(amount.value[gt]=1000,amount.value[lt]=50000) +``` + +## Examples + +**Filter by ID:** + +```bash +?filters=id[eq]=399ca839-abd5-4a7d-981b-f187e7777ec8 +``` + +**Filter by date range:** + +```bash +?filters=and(createdAt[gte]=2025-01-01T00:00:00Z,createdAt[lte]=2025-01-31T23:59:59Z) +``` + +**Filter by type (multiple values):** + +```bash +?filters=type[in]=CHECKING|SAVING +``` + +**Filter by current status:** + +```bash +?filters=states.status[last]=ACTIVE +``` + +**Combined filters:** + +```bash +?filters=states.status[last]=ACTIVE;and(amount.value[gt]=1000,amount.value[lt]=50000) +``` + +Full request example: + +```bash +curl --request GET \ + --url '{{sandboxUrl}}/api/v1/accounts?limit=10&filters=states.status[last]=ACTIVE' \ + --header 'Authorization: Bearer ' +``` diff --git a/guides/principles/idempotency.mdx b/guides/principles/idempotency.mdx index d603df3..26c9400 100644 --- a/guides/principles/idempotency.mdx +++ b/guides/principles/idempotency.mdx @@ -3,4 +3,72 @@ title: "Idempotency" description: "Use the X-Idempotency-Key header to safely retry requests." --- -{/* TODO: Write idempotency guide — X-Idempotency-Key header, when required, conflict behavior (409) */} +## Overview + +Idempotency ensures that performing the same operation multiple times produces the same result as performing it once. This protects against duplicate operations when requests are retried due to timeouts or network failures. + +## How it works + +### The X-Idempotency-Key header + +Mutating endpoints (POST, PUT, PATCH) require the `X-Idempotency-Key` header. This client-generated key lets the API detect and deduplicate repeated requests. + +```bash +curl --request POST \ + --url {{sandboxUrl}}/api/v1/operations \ + --header 'Authorization: Bearer ' \ + --header 'X-Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000' \ + --header 'Content-Type: application/json' \ + --data '{ ... }' +``` + +### When it is required + +The `X-Idempotency-Key` header is required on any request that creates or modifies a resource. GET and DELETE requests do not require it. + +### Conflict behavior + +If a request arrives with an idempotency key that was already used for a **different** request body, the API returns HTTP `409 Conflict`: + +```json +{ + "code": "IDEMPOTENT_ID_CONFLICT", + "message": "A request with this idempotency key has already been processed.", + "details": {} +} +``` + +If the key matches a previous request with the **same** body, the API returns the original response without reprocessing. + +## Examples + +Generate a UUID v4 for each unique operation: + + + + ```python + import uuid + + idempotency_key = str(uuid.uuid4()) + ``` + + + ```javascript + const idempotencyKey = crypto.randomUUID(); + ``` + + + +Retrying safely after a timeout: + +```bash +# First attempt — times out +curl ... --header 'X-Idempotency-Key: 550e8400-...' + +# Retry with the same key — safe, returns original result +curl ... --header 'X-Idempotency-Key: 550e8400-...' +``` + + + Never reuse an idempotency key for a different operation. Each unique operation must have its own key. + diff --git a/guides/principles/money.mdx b/guides/principles/money.mdx index ca92745..a9681a6 100644 --- a/guides/principles/money.mdx +++ b/guides/principles/money.mdx @@ -3,4 +3,84 @@ title: "Money and currencies" description: "How monetary amounts are represented in the API." --- -{/* TODO: Write money guide — value in minor units (integer), asset as ISO 4217 currency code, supported currencies */} +## Overview + +All monetary amounts in the Trace FX API are represented as integers in **minor units** (e.g. cents) paired with an ISO 4217 currency code. This avoids floating-point precision issues. + +## How it works + +### Amount object + +Every amount in the API uses the same structure: + +```json +{ + "value": 500000, + "asset": "BRL" +} +``` + +| Field | Type | Description | +|---|---|---| +| `value` | integer | Amount in the smallest currency unit (e.g. centavos for BRL, cents for USD) | +| `asset` | string | ISO 4217 currency code | + +### Converting to display values + +Divide the `value` by the currency's minor unit factor (typically 100) to get the display amount: + +| API value | Asset | Display amount | +|---|---|---| +| `500000` | `BRL` | R$ 5.000,00 | +| `1050` | `USD` | $10.50 | +| `100000000` | `BTC` | 1.00000000 BTC | + + + Always use integer arithmetic when working with amounts. Never convert to floating-point for calculations. + + +### Date and time format + +All dates and timestamps use **UTC** in ISO 8601 format: + +``` +yyyy-MM-ddTHH:mm:ss.SSSZ +``` + +Example: `2025-01-15T14:30:00.000Z` + +| Component | Description | Example | +|---|---|---| +| `yyyy` | Four-digit year | `2025` | +| `MM` | Two-digit month (01–12) | `01` | +| `dd` | Two-digit day (01–31) | `15` | +| `T` | Date/time separator | `T` | +| `HH` | Hours in 24-hour format (00–23) | `14` | +| `mm` | Minutes (00–59) | `30` | +| `ss` | Seconds (00–59) | `00` | +| `.SSS` | Milliseconds (optional) | `.000` | +| `Z` | UTC timezone indicator | `Z` | + +## Examples + +A deposit of R$ 1.250,00: + +```json +{ + "amount": { + "value": 125000, + "asset": "BRL" + } +} +``` + +A withdrawal of $500.00: + +```json +{ + "amount": { + "value": 50000, + "asset": "USD" + } +} +``` diff --git a/guides/principles/pagination.mdx b/guides/principles/pagination.mdx index f75fec8..16c0ab1 100644 --- a/guides/principles/pagination.mdx +++ b/guides/principles/pagination.mdx @@ -3,4 +3,80 @@ title: "Pagination" description: "Navigate large result sets with cursor-based pagination." --- -{/* TODO: Write pagination guide — cursor-based, meta object (previousCursor, nextCursor, total, totalMatches) */} +## Overview + +List endpoints return paginated responses using cursor-based pagination. This approach provides stable results even when data changes between requests. + +## How it works + +### Query parameters + +| Parameter | Type | Default | Description | +|---|---|---|---| +| `limit` | integer | `10` | Number of items per page | +| `cursor` | string | — | Cursor returned from a previous response | + +### Response structure + +Every list response includes a `meta` object alongside the `data` array: + +```json +{ + "data": [ + { "id": "acc_001", "type": "CHECKING" }, + { "id": "acc_002", "type": "SAVING" } + ], + "meta": { + "previousCursor": null, + "nextCursor": "eyJpZCI6ImFjY18wMDIifQ", + "total": 45, + "totalMatches": 45 + } +} +``` + +| Field | Description | +|---|---| +| `previousCursor` | Cursor to fetch the previous page. `null` on the first page. | +| `nextCursor` | Cursor to fetch the next page. `null` on the last page. | +| `total` | Total number of items in the collection. | +| `totalMatches` | Total items matching the current filters. | + +## Examples + +**First page:** + +```bash +curl --request GET \ + --url '{{sandboxUrl}}/api/v1/accounts?limit=10' \ + --header 'Authorization: Bearer ' +``` + +**Next page** — pass the `nextCursor` value as `cursor`: + +```bash +curl --request GET \ + --url '{{sandboxUrl}}/api/v1/accounts?limit=10&cursor=eyJpZCI6ImFjY18wMDIifQ' \ + --header 'Authorization: Bearer ' +``` + +**Iterating through all pages:** + +```python +cursor = None + +while True: + params = {"limit": 10} + if cursor: + params["cursor"] = cursor + + response = client.get("/api/v1/accounts", params=params) + data = response.json() + + for item in data["data"]: + process(item) + + cursor = data["meta"]["nextCursor"] + if cursor is None: + break +``` diff --git a/guides/principles/versioning.mdx b/guides/principles/versioning.mdx index 8f236cd..6cf41aa 100644 --- a/guides/principles/versioning.mdx +++ b/guides/principles/versioning.mdx @@ -3,4 +3,68 @@ title: "Versioning" description: "How API versioning works via the X-Trace-Version header." --- -{/* TODO: Write versioning guide — X-Trace-Version header, default version behavior, deprecation policy */} +## Overview + +The Trace FX API uses the `X-Trace-Version` header to route requests to a specific API version. This lets you pin your integration to a known version while newer versions are released. + +## How it works + +### Setting the version + +Include the `X-Trace-Version` header in your requests: + +```bash +curl --request GET \ + --url {{sandboxUrl}}/api/v1/accounts \ + --header 'Authorization: Bearer ' \ + --header 'X-Trace-Version: 2' +``` + +If the header is omitted, empty, or contains a non-existent version, the request is routed to the **default version** configured by Trace. + + + Always pin a version in production to avoid unexpected behavior when the default changes. + + +### Non-breaking changes + +The following changes do not require a new version. They can be released at any time: + +- Adding new **optional** fields to responses +- Adding new endpoints +- Adding new optional query parameters + + + Configure your client to ignore unknown fields in responses. Strict validation may cause failures when optional fields are added. + + +### Breaking changes + +The following changes trigger a **new major version**: + +- Removing or renaming existing fields +- Changing a field's data type (e.g. integer to string) +- Making an optional field required +- Reorganizing the response structure +- Changing endpoint behavior, status codes, or error responses +- Adding new enum values that affect strict validation + +### Deprecation policy + +When a new version is released, the previous version remains available for a migration period. Deprecation timelines are communicated in advance so you can plan your upgrade. + +## Examples + +Pinned version: + +```bash +X-Trace-Version: 2 +``` + +No version header (uses default): + +```bash +curl --request GET \ + --url {{sandboxUrl}}/api/v1/accounts \ + --header 'Authorization: Bearer ' +``` diff --git a/webhooks/events.mdx b/webhooks/events.mdx index 4e91367..9b28d02 100644 --- a/webhooks/events.mdx +++ b/webhooks/events.mdx @@ -4,8 +4,54 @@ description: "Complete catalog of webhook events grouped by domain." sidebarTitle: "Event reference" --- -{/* TODO: Write event reference — grouped by domain: - Account events: account.created, account.activated, account.deactivated, etc. - Payment events: operation.created, operation.completed, operation.failed, etc. - Beneficiary events: beneficiary.created, beneficiary.approved, beneficiary.rejected -*/} +## Overview + +Each webhook event has a `type` field that identifies what happened. Events are grouped by domain. + +All events share this structure: + +```json +{ + "messageId": "evt_550e8400-e29b-41d4-a716-446655440000", + "type": "account.activated", + "timestamp": "2025-01-15T14:30:00.000Z", + "data": { } +} +``` + +| Field | Description | +|---|---| +| `messageId` | Unique event identifier. Use for deduplication and signature verification. | +| `type` | Event type (see tables below). | +| `timestamp` | When the event occurred (ISO 8601 UTC). | +| `data` | Event-specific payload. | + +## Account events + +Triggered when account lifecycle changes occur. + +| Event type | Description | +|---|---| +| `account.created` | A new account was created. | +| `account.activated` | An account is now active and ready for use. | +| `account.deactivated` | An account was deactivated. | + +## Payment events + +Triggered when payment operations change state. + +| Event type | Description | +|---|---| +| `operation.created` | A new operation (deposit, withdrawal, or swap) was created. | +| `operation.completed` | An operation finished successfully. | +| `operation.failed` | An operation failed. Check `data` for the failure reason. | + +## Beneficiary events + +Triggered when beneficiary records change. + +| Event type | Description | +|---|---| +| `beneficiary.created` | A new beneficiary was registered. | +| `beneficiary.approved` | A beneficiary passed compliance review. | +| `beneficiary.rejected` | A beneficiary was rejected during compliance review. | diff --git a/webhooks/overview.mdx b/webhooks/overview.mdx index c9789f1..44e2455 100644 --- a/webhooks/overview.mdx +++ b/webhooks/overview.mdx @@ -4,4 +4,110 @@ description: "Receive real-time notifications when events occur on the Trace FX sidebarTitle: "Overview" --- -{/* TODO: Write webhook overview — setup, endpoint requirements, signatures, retry policy, delivery guarantees */} +## Overview + +Webhooks notify your application in real time when events occur on the Trace FX platform — such as an account being activated, a payment completing, or a beneficiary being approved. Instead of polling the API, you register an endpoint and Trace pushes events to it. + +## Setup + +### Endpoint requirements + +Your webhook endpoint must: + +- Accept **POST** requests with a JSON body. +- Be reachable over **HTTPS**. +- Return a **2xx** status code within **10 seconds** to acknowledge receipt. + +If your endpoint returns a non-2xx status or times out, Trace treats the delivery as failed and schedules a retry. + +### Registering your endpoint + +Webhook endpoints are configured during onboarding. Contact your Trace integration manager to register or update your endpoint URL. + +## Signatures + +Every webhook request includes an `X-Message-Signature` header containing an HMAC-SHA256 hash. Use this to verify that the request came from Trace and was not tampered with. + +### Verification steps + +1. Extract the `messageId` from the request body. +2. Concatenate `{messageId}+{clientId}` — the `+` is a literal character, not string concatenation. +3. Compute the HMAC-SHA256 hash using your **client secret** as the key. +4. Compare the result with the `X-Message-Signature` header value. + +### Code examples + + + + ```python + import hashlib + import hmac + + def verify_signature(message_id, client_id, client_secret, received_signature): + payload = f"{message_id}+{client_id}" + computed = hmac.new( + client_secret.encode(), + payload.encode(), + hashlib.sha256 + ).hexdigest() + return hmac.compare_digest(computed, received_signature) + ``` + + + ```javascript + const crypto = require("crypto"); + + function verifySignature(messageId, clientId, clientSecret, receivedSignature) { + const payload = `${messageId}+${clientId}`; + const computed = crypto + .createHmac("sha256", clientSecret) + .update(payload) + .digest("hex"); + return crypto.timingSafeEqual( + Buffer.from(computed), + Buffer.from(receivedSignature) + ); + } + ``` + + + ```kotlin + import javax.crypto.Mac + import javax.crypto.spec.SecretKeySpec + + fun verifySignature( + messageId: String, clientId: String, + clientSecret: String, receivedSignature: String + ): Boolean { + val payload = "$messageId+$clientId" + val mac = Mac.getInstance("HmacSHA256") + mac.init(SecretKeySpec(clientSecret.toByteArray(), "HmacSHA256")) + val computed = mac.doFinal(payload.toByteArray()) + .joinToString("") { "%02x".format(it) } + return computed == receivedSignature + } + ``` + + + + + Always use constant-time comparison when verifying signatures to prevent timing attacks. + + +## Retry policy + +If delivery fails, Trace retries with exponential backoff: + +| Attempt | Delay | +|---|---| +| 1st retry | 1 minute | +| 2nd retry | 5 minutes | +| 3rd retry | 30 minutes | +| 4th retry | 2 hours | +| 5th retry | 24 hours | + +After all retries are exhausted, the event is marked as failed. Events are delivered **at least once** — your endpoint should be idempotent to handle potential duplicates. + + + Use the `messageId` field to deduplicate events on your side. + From 31541e37b07a0c44d8a88660c7c23390129b58c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1rio=20Alvial?= Date: Tue, 14 Apr 2026 12:59:30 -0300 Subject: [PATCH 3/8] docs: write quickstart and journey guides - Quickstart: 3-step flow (get token, list accounts, check response) - Open BRL account: create, wait for activation, get funding instructions - Open crypto account: create, compliance checks, get wallet address - Deposit: intent-first and money-first flows - Withdrawal: beneficiary registration, compliance review, tracking - Swap: quote, execute, forward-only warning Journey pages link to webhook events instead of duplicating descriptions. --- journeys/deposit.mdx | 54 ++++++++++++++++++++++++- journeys/open-brl-account.mdx | 47 +++++++++++++++++++++- journeys/open-crypto-account.mdx | 47 +++++++++++++++++++++- journeys/swap.mdx | 68 +++++++++++++++++++++++++++++++- journeys/withdrawal.mdx | 64 +++++++++++++++++++++++++++++- quickstart.mdx | 49 ++++++++++++++++++++++- 6 files changed, 323 insertions(+), 6 deletions(-) diff --git a/journeys/deposit.mdx b/journeys/deposit.mdx index 5a6116e..566d393 100644 --- a/journeys/deposit.mdx +++ b/journeys/deposit.mdx @@ -3,4 +3,56 @@ title: "Make a deposit" description: "How to create a deposit and track it through to completion." --- -{/* TODO: Write journey — intent-first and money-first deposit flows, webhook events, status transitions */} +import SandboxNote from '/snippets/sandbox-note.mdx'; + + + +## Overview + +Deposits fund an account with BRL (via PIX or bank transfer) or crypto (via blockchain transfer). There are two flows depending on whether you initiate the deposit through the API first or the funds arrive first. + +## Prerequisites + +- An [active account](/journeys/open-brl-account) to deposit into. +- Valid [authentication credentials](/guides/authentication). + +## Steps + +### Intent-first flow + +Use this when you create the deposit intent before the funds arrive. + + + + ```bash + curl --request POST \ + --url {{sandboxUrl}}/api/v1/operations \ + --header 'Authorization: Bearer ' \ + + --header 'X-Idempotency-Key: ' \ + --header 'Content-Type: application/json' \ + --data '{ + "type": "DEPOSIT", + "accountId": "", + "amount": { + "value": 500000, + "asset": "BRL" + } + }' + ``` + + The operation is created in `PENDING` status. + + + Listen for `operation.created`, `operation.completed`, and `operation.failed` [webhook events](/webhooks/events) as the deposit progresses. + + + +### Money-first flow + +When funds arrive at the account before an explicit deposit intent is created, the platform automatically creates the operation and notifies you via [webhook events](/webhooks/events). + +## What happens next + +- [Make a withdrawal](/journeys/withdrawal) — move funds out of the account. +- [Execute a swap](/journeys/swap) — convert between currencies. diff --git a/journeys/open-brl-account.mdx b/journeys/open-brl-account.mdx index b79b49c..8e8cd65 100644 --- a/journeys/open-brl-account.mdx +++ b/journeys/open-brl-account.mdx @@ -3,4 +3,49 @@ title: "Open a BRL account" description: "Step-by-step guide to opening a Brazilian Real account for an account owner." --- -{/* TODO: Write journey — end-to-end flow from create-account to active account with BRL funding instructions */} +import SandboxNote from '/snippets/sandbox-note.mdx'; + + + +## Overview + +This guide walks you through creating a BRL (Brazilian Real) account for an account owner. Once the account is active, it can receive deposits via PIX and bank transfer. + +## Prerequisites + +- Valid [authentication credentials](/guides/authentication). +- Account owner details (name, document number, date of birth). + +## Steps + + + + Submit a request with the owner's details and the account currency: + + ```bash + curl --request POST \ + --url {{sandboxUrl}}/api/v1/accounts \ + --header 'Authorization: Bearer ' \ + + --header 'X-Idempotency-Key: ' \ + --header 'Content-Type: application/json' \ + --data '{ + "asset": "BRL", + "type": "CHECKING" + }' + ``` + + The account is returned in `PENDING` status. + + + The account goes through internal validation. Listen for the [`account.activated`](/webhooks/events) webhook event or poll the account endpoint until the status changes to `ACTIVE`. + + + Once the account is active, retrieve its banking details (branch, account number) to share with the account owner for incoming transfers. + + + +## What happens next + +- [Make a deposit](/journeys/deposit) — fund the account via PIX or bank transfer. +- [Webhooks](/webhooks/overview) — set up real-time notifications for account events. diff --git a/journeys/open-crypto-account.mdx b/journeys/open-crypto-account.mdx index 4fb5e98..4d8215f 100644 --- a/journeys/open-crypto-account.mdx +++ b/journeys/open-crypto-account.mdx @@ -3,4 +3,49 @@ title: "Open a crypto account" description: "Step-by-step guide to opening a crypto asset account for an account owner." --- -{/* TODO: Write journey — end-to-end flow from create-account to active account with crypto wallet */} +import SandboxNote from '/snippets/sandbox-note.mdx'; + + + +## Overview + +This guide walks you through creating a crypto asset account for an account owner. Once active, the account provides a wallet address for receiving crypto deposits. + +## Prerequisites + +- Valid [authentication credentials](/guides/authentication). +- Account owner details. + +## Steps + + + + Submit a request specifying the crypto asset: + + ```bash + curl --request POST \ + --url {{sandboxUrl}}/api/v1/accounts \ + --header 'Authorization: Bearer ' \ + + --header 'X-Idempotency-Key: ' \ + --header 'Content-Type: application/json' \ + --data '{ + "asset": "BTC", + "type": "CRYPTO" + }' + ``` + + The account is returned in `PENDING` status. + + + The account goes through compliance checks. Listen for the [`account.activated`](/webhooks/events) webhook event or poll the account endpoint until the status changes to `ACTIVE`. + + + Once active, retrieve the account details to obtain the wallet address for receiving crypto deposits. + + + +## What happens next + +- [Make a deposit](/journeys/deposit) — receive crypto into the wallet. +- [Execute a swap](/journeys/swap) — convert crypto to BRL or other currencies. diff --git a/journeys/swap.mdx b/journeys/swap.mdx index e83c649..89ff7c2 100644 --- a/journeys/swap.mdx +++ b/journeys/swap.mdx @@ -3,4 +3,70 @@ title: "Execute a swap" description: "How to convert between currencies using the swap endpoint." --- -{/* TODO: Write journey — swap flow, quote integration, forward-only execution */} +import SandboxNote from '/snippets/sandbox-note.mdx'; + + + +## Overview + +Swaps convert funds between currencies within the platform — for example, BRL to USD or BTC to BRL. The flow is forward-only: once a swap is executed at the quoted rate, it cannot be reversed. + +## Prerequisites + +- An [active account](/journeys/open-brl-account) with sufficient balance in the source currency. +- Valid [authentication credentials](/guides/authentication). + +## Steps + + + + Get the current exchange rate and confirm the amounts before executing: + + ```bash + curl --request POST \ + --url {{sandboxUrl}}/api/v1/quotes \ + --header 'Authorization: Bearer ' \ + + --header 'Content-Type: application/json' \ + --data '{ + "from": "BRL", + "to": "USD", + "amount": { + "value": 500000, + "asset": "BRL" + } + }' + ``` + + The response includes the quoted rate and the resulting amount. Quotes are valid for a limited time. + + + Submit the swap using the quote ID: + + ```bash + curl --request POST \ + --url {{sandboxUrl}}/api/v1/operations \ + --header 'Authorization: Bearer ' \ + + --header 'X-Idempotency-Key: ' \ + --header 'Content-Type: application/json' \ + --data '{ + "type": "SWAP", + "quoteId": "", + "accountId": "" + }' + ``` + + + Listen for `operation.created`, `operation.completed`, and `operation.failed` [webhook events](/webhooks/events). + + + + + Swaps are forward-only. Once executed, they cannot be reversed. Always verify the quoted rate before submitting. + + +## What happens next + +- [Make a withdrawal](/journeys/withdrawal) — send the converted funds to an external destination. +- [Webhooks](/webhooks/overview) — monitor swap events in real time. diff --git a/journeys/withdrawal.mdx b/journeys/withdrawal.mdx index 3aacb66..7f4387f 100644 --- a/journeys/withdrawal.mdx +++ b/journeys/withdrawal.mdx @@ -3,4 +3,66 @@ title: "Make a withdrawal" description: "How to create a withdrawal and handle the review process." --- -{/* TODO: Write journey — withdrawal flow with and without compliance review, beneficiary setup */} +import SandboxNote from '/snippets/sandbox-note.mdx'; + + + +## Overview + +Withdrawals move funds from an account to an external destination — a bank account (via TED or PIX) or a crypto wallet. Some withdrawals go through a compliance review before being processed. + +## Prerequisites + +- An [active account](/journeys/open-brl-account) with sufficient balance. +- A registered **beneficiary** for the destination (bank account or wallet). +- Valid [authentication credentials](/guides/authentication). + +## Steps + + + + Before withdrawing to a new destination, register the beneficiary: + + ```bash + curl --request POST \ + --url {{sandboxUrl}}/api/v1/beneficiaries \ + --header 'Authorization: Bearer ' \ + + --header 'X-Idempotency-Key: ' \ + --header 'Content-Type: application/json' \ + --data '{ + "name": "Beneficiary Name", + "type": "BANK_ACCOUNT" + }' + ``` + + Wait for the [`beneficiary.approved`](/webhooks/events) webhook event before proceeding. The beneficiary may be rejected during compliance review. + + + ```bash + curl --request POST \ + --url {{sandboxUrl}}/api/v1/operations \ + --header 'Authorization: Bearer ' \ + + --header 'X-Idempotency-Key: ' \ + --header 'Content-Type: application/json' \ + --data '{ + "type": "WITHDRAWAL", + "accountId": "", + "beneficiaryId": "", + "amount": { + "value": 100000, + "asset": "BRL" + } + }' + ``` + + + The withdrawal may go through compliance review. Listen for `operation.created`, `operation.completed`, and `operation.failed` [webhook events](/webhooks/events). + + + +## What happens next + +- [Execute a swap](/journeys/swap) — convert currencies before withdrawing. +- [Webhooks](/webhooks/overview) — monitor all operation events. diff --git a/quickstart.mdx b/quickstart.mdx index 587714c..2dcd6f4 100644 --- a/quickstart.mdx +++ b/quickstart.mdx @@ -3,4 +3,51 @@ title: "Quickstart" description: "Make your first API request to Trace FX in under five minutes." --- -{/* TODO: Write quickstart — get credentials, hit sandbox, see a response */} +import SandboxNote from '/snippets/sandbox-note.mdx'; + + + +## Prerequisites + +- A **client ID** and **client secret** provided during onboarding. +- [curl](https://curl.se/) or any HTTP client. + +## Steps + + + + Follow the [Authentication](/guides/authentication) guide to exchange your client credentials for a JWT. Copy the `access_token` from the response. + + + List accounts in the sandbox: + + ```bash + curl --request GET \ + --url {{sandboxUrl}}/api/v1/accounts \ + --header 'Authorization: Bearer YOUR_ACCESS_TOKEN' + ``` + + + You should see a paginated response: + + ```json + { + "data": [], + "meta": { + "previousCursor": null, + "nextCursor": null, + "total": 0, + "totalMatches": 0 + } + } + ``` + + An empty `data` array is expected — you haven't created any accounts yet. You're connected and authenticated. + + + +## What's next + +- [Open a BRL account](/journeys/open-brl-account) — create your first account +- [Authentication](/guides/authentication) — learn about token management +- [Environments](/guides/environments) — understand sandbox vs production From 786c6ce208223a5a6b064726c54267bf3a4f0215 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1rio=20Alvial?= Date: Tue, 14 Apr 2026 13:04:11 -0300 Subject: [PATCH 4/8] chore: add .idea to gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index c18dd8d..8ae8ce5 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ __pycache__/ +.idea/ From 83335d04ef3e520b5b4940613e27c14fa9ed8f33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1rio=20Alvial?= Date: Tue, 14 Apr 2026 14:56:38 -0300 Subject: [PATCH 5/8] fix: add spaces to markdown table separators for MD060 lint rule --- guides/environments.mdx | 2 +- guides/principles/errors.mdx | 8 ++++---- guides/principles/filtering.mdx | 2 +- guides/principles/money.mdx | 6 +++--- guides/principles/pagination.mdx | 4 ++-- webhooks/events.mdx | 8 ++++---- webhooks/overview.mdx | 2 +- 7 files changed, 16 insertions(+), 16 deletions(-) diff --git a/guides/environments.mdx b/guides/environments.mdx index 2fbeb8f..cd53901 100644 --- a/guides/environments.mdx +++ b/guides/environments.mdx @@ -8,7 +8,7 @@ description: "Sandbox and production base URLs, and what differs between them." Trace FX provides two environments. Use sandbox for development and testing, and production for live integrations. | Environment | Base URL | -|---|---| +| --- | --- | | Sandbox | `{{sandboxUrl}}` | | Production | `{{productionUrl}}` | diff --git a/guides/principles/errors.mdx b/guides/principles/errors.mdx index 7cab2a1..a5007eb 100644 --- a/guides/principles/errors.mdx +++ b/guides/principles/errors.mdx @@ -22,7 +22,7 @@ All errors follow the same shape: ``` | Field | Type | Description | -|---|---|---| +| --- | --- | --- | | `code` | string | Machine-readable error code for programmatic handling | | `message` | string | Human-readable description of what went wrong | | `details` | object | Additional context (may be empty) | @@ -30,7 +30,7 @@ All errors follow the same shape: ### HTTP status codes | Code | Meaning | When it happens | -|---|---|---| +| --- | --- | --- | | `200` | OK | Request succeeded | | `201` | Created | Resource was created | | `204` | No content | Request succeeded with no response body | @@ -46,7 +46,7 @@ All errors follow the same shape: ### Common error codes | Code | HTTP status | Description | -|---|---|---| +| --- | --- | --- | | `INVALID_DATA` | 400 | Request body failed validation | | `RESOURCE_NOT_FOUND` | 404 | The requested resource does not exist | | `IDEMPOTENT_ID_CONFLICT` | 409 | Idempotency key was already used | @@ -56,7 +56,7 @@ All errors follow the same shape: ### Retry guidance | Status code | Should retry? | Strategy | -|---|---|---| +| --- | --- | --- | | `400`, `401`, `404`, `422` | No | Fix the request before retrying | | `408`, `429` | Yes | Back off and retry after the indicated period | | `409` | Depends | Check if the original request succeeded | diff --git a/guides/principles/filtering.mdx b/guides/principles/filtering.mdx index 29bd646..d2cb24a 100644 --- a/guides/principles/filtering.mdx +++ b/guides/principles/filtering.mdx @@ -22,7 +22,7 @@ Combine multiple filters with a semicolon (`;`). Group conditions with logical o ### Operators | Operator | Description | Example | -|---|---|---| +| --- | --- | --- | | `eq` | Equals | `id[eq]=abc-123` | | `gt` | Greater than | `amount.value[gt]=1000` | | `gte` | Greater than or equal | `createdAt[gte]=2025-01-01T00:00:00Z` | diff --git a/guides/principles/money.mdx b/guides/principles/money.mdx index a9681a6..f98c158 100644 --- a/guides/principles/money.mdx +++ b/guides/principles/money.mdx @@ -21,7 +21,7 @@ Every amount in the API uses the same structure: ``` | Field | Type | Description | -|---|---|---| +| --- | --- | --- | | `value` | integer | Amount in the smallest currency unit (e.g. centavos for BRL, cents for USD) | | `asset` | string | ISO 4217 currency code | @@ -30,7 +30,7 @@ Every amount in the API uses the same structure: Divide the `value` by the currency's minor unit factor (typically 100) to get the display amount: | API value | Asset | Display amount | -|---|---|---| +| --- | --- | --- | | `500000` | `BRL` | R$ 5.000,00 | | `1050` | `USD` | $10.50 | | `100000000` | `BTC` | 1.00000000 BTC | @@ -50,7 +50,7 @@ yyyy-MM-ddTHH:mm:ss.SSSZ Example: `2025-01-15T14:30:00.000Z` | Component | Description | Example | -|---|---|---| +| --- | --- | --- | | `yyyy` | Four-digit year | `2025` | | `MM` | Two-digit month (01–12) | `01` | | `dd` | Two-digit day (01–31) | `15` | diff --git a/guides/principles/pagination.mdx b/guides/principles/pagination.mdx index 16c0ab1..9187661 100644 --- a/guides/principles/pagination.mdx +++ b/guides/principles/pagination.mdx @@ -12,7 +12,7 @@ List endpoints return paginated responses using cursor-based pagination. This ap ### Query parameters | Parameter | Type | Default | Description | -|---|---|---|---| +| --- | --- | --- | --- | | `limit` | integer | `10` | Number of items per page | | `cursor` | string | — | Cursor returned from a previous response | @@ -36,7 +36,7 @@ Every list response includes a `meta` object alongside the `data` array: ``` | Field | Description | -|---|---| +| --- | --- | | `previousCursor` | Cursor to fetch the previous page. `null` on the first page. | | `nextCursor` | Cursor to fetch the next page. `null` on the last page. | | `total` | Total number of items in the collection. | diff --git a/webhooks/events.mdx b/webhooks/events.mdx index 9b28d02..96b3f4d 100644 --- a/webhooks/events.mdx +++ b/webhooks/events.mdx @@ -20,7 +20,7 @@ All events share this structure: ``` | Field | Description | -|---|---| +| --- | --- | | `messageId` | Unique event identifier. Use for deduplication and signature verification. | | `type` | Event type (see tables below). | | `timestamp` | When the event occurred (ISO 8601 UTC). | @@ -31,7 +31,7 @@ All events share this structure: Triggered when account lifecycle changes occur. | Event type | Description | -|---|---| +| --- | --- | | `account.created` | A new account was created. | | `account.activated` | An account is now active and ready for use. | | `account.deactivated` | An account was deactivated. | @@ -41,7 +41,7 @@ Triggered when account lifecycle changes occur. Triggered when payment operations change state. | Event type | Description | -|---|---| +| --- | --- | | `operation.created` | A new operation (deposit, withdrawal, or swap) was created. | | `operation.completed` | An operation finished successfully. | | `operation.failed` | An operation failed. Check `data` for the failure reason. | @@ -51,7 +51,7 @@ Triggered when payment operations change state. Triggered when beneficiary records change. | Event type | Description | -|---|---| +| --- | --- | | `beneficiary.created` | A new beneficiary was registered. | | `beneficiary.approved` | A beneficiary passed compliance review. | | `beneficiary.rejected` | A beneficiary was rejected during compliance review. | diff --git a/webhooks/overview.mdx b/webhooks/overview.mdx index 44e2455..8ed05c7 100644 --- a/webhooks/overview.mdx +++ b/webhooks/overview.mdx @@ -99,7 +99,7 @@ Every webhook request includes an `X-Message-Signature` header containing an HMA If delivery fails, Trace retries with exponential backoff: | Attempt | Delay | -|---|---| +| --- | --- | | 1st retry | 1 minute | | 2nd retry | 5 minutes | | 3rd retry | 30 minutes | From 25c3a7fa6b3e7d40cf04befae8d30b407a01bd04 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1rio=20Alvial?= Date: Tue, 14 Apr 2026 16:27:17 -0300 Subject: [PATCH 6/8] fix: correct API content against real service implementations - Authentication: replace Auth0 with Trace auth service, fix endpoint to /api/oauth/client/token, camelCase fields, 1h token lifetime - URLs: remove /v1/ path segment (versioning is header-based) - Pagination: add missing params (direction, sortOrder, includeTotalMatches), fix total field description - Filtering: add missing operators (ne, text), fix like behavior - Money: add decimalValue and decimals fields to amount object - Date/time: split into its own page (guides/principles/datetime) - Idempotency: simplify conflict behavior (same key = conflict, no body check) - Webhooks: fix retry policy (constant 5min, 288 attempts), fix 5s timeout, remove section from navigation until properly documented --- docs.json | 8 +---- guides/authentication.mdx | 30 +++++++---------- guides/principles/datetime.mdx | 46 ++++++++++++++++++++++++++ guides/principles/filtering.mdx | 16 +++++---- guides/principles/idempotency.mdx | 6 ++-- guides/principles/money.mdx | 54 +++++++++++-------------------- guides/principles/pagination.mdx | 15 +++++---- guides/principles/versioning.mdx | 4 +-- journeys/deposit.mdx | 2 +- journeys/open-brl-account.mdx | 2 +- journeys/open-crypto-account.mdx | 2 +- journeys/swap.mdx | 4 +-- journeys/withdrawal.mdx | 4 +-- quickstart.mdx | 2 +- webhooks/overview.mdx | 14 ++------ 15 files changed, 112 insertions(+), 97 deletions(-) create mode 100644 guides/principles/datetime.mdx diff --git a/docs.json b/docs.json index 5614b77..1cc0c28 100644 --- a/docs.json +++ b/docs.json @@ -58,6 +58,7 @@ "guides/principles/pagination", "guides/principles/filtering", "guides/principles/money", + "guides/principles/datetime", "guides/principles/errors" ] }, @@ -70,13 +71,6 @@ "journeys/withdrawal", "journeys/swap" ] - }, - { - "group": "Webhooks", - "pages": [ - "webhooks/overview", - "webhooks/events" - ] } ] } diff --git a/guides/authentication.mdx b/guides/authentication.mdx index b7dfdde..c6c1b16 100644 --- a/guides/authentication.mdx +++ b/guides/authentication.mdx @@ -1,30 +1,28 @@ --- title: "Authentication" -description: "Obtain and use Auth0 JWT tokens to authenticate requests to the Trace FX API." +description: "Obtain and use JWT tokens to authenticate requests to the Trace FX API." sidebarTitle: "Authentication" --- ## Overview -The Trace FX API uses [Auth0](https://auth0.com/) for authentication and authorization. Every request must include a valid JSON Web Token (JWT) in the `Authorization` header. +Every request to the Trace FX API must include a valid JSON Web Token (JWT) in the `Authorization` header. -During onboarding you receive a **client ID** and **client secret**. Use these to obtain an access token through the OAuth 2.0 client credentials flow. +During onboarding you receive a **client ID** and **client secret**. Use these to obtain an access token from the Trace authentication service. ## Details ### Obtaining a token -Request an access token from the Auth0 token endpoint: +Request an access token from the token endpoint: ```bash curl --request POST \ - --url {{authUrl}}/oauth/token \ + --url {{authUrl}}/api/oauth/client/token \ --header 'Content-Type: application/json' \ --data '{ - "client_id": "YOUR_CLIENT_ID", - "client_secret": "YOUR_CLIENT_SECRET", - "audience": "{{productionUrl}}", - "grant_type": "client_credentials" + "clientId": "YOUR_CLIENT_ID", + "clientSecret": "YOUR_CLIENT_SECRET" }' ``` @@ -32,23 +30,19 @@ A successful response includes the token and its lifetime: ```json { - "access_token": "eyJhbGciOiJSUzI1NiIs...", - "token_type": "Bearer", - "expires_in": 86400 + "accessToken": "eyJhbGciOiJSUzI1NiIs...", + "tokenType": "Bearer", + "expiresIn": 3600 } ``` - - In sandbox, set the `audience` to `{{sandboxUrl}}`. - - ### Using the token Include the token in the `Authorization` header of every request: ```bash curl --request GET \ - --url {{sandboxUrl}}/api/v1/accounts \ + --url {{sandboxUrl}}/api/accounts \ --header 'Authorization: Bearer eyJhbGciOiJSUzI1NiIs...' ``` @@ -56,7 +50,7 @@ The token contains your customer identity — no separate customer ID header is ### Token lifecycle -Tokens are valid for **24 hours** (86,400 seconds). Follow these best practices: +Tokens are valid for **1 hour** (3,600 seconds). Follow these best practices: 1. **Store securely** — keep the token in memory after obtaining it. 2. **Check before use** — inspect the `exp` claim in the JWT payload to confirm it has not expired. diff --git a/guides/principles/datetime.mdx b/guides/principles/datetime.mdx new file mode 100644 index 0000000..4f212aa --- /dev/null +++ b/guides/principles/datetime.mdx @@ -0,0 +1,46 @@ +--- +title: "Date and time" +description: "How dates and timestamps are formatted in the API." +--- + +## Overview + +All dates and timestamps in the Trace FX API use **UTC** in ISO 8601 format. No other timezones are accepted or returned. + +## How it works + +### Format + +``` +yyyy-MM-ddTHH:mm:ss.SSSZ +``` + +Example: `2025-01-15T14:30:00.000Z` + +| Component | Description | Example | +| --- | --- | --- | +| `yyyy` | Four-digit year | `2025` | +| `MM` | Two-digit month (01–12) | `01` | +| `dd` | Two-digit day (01–31) | `15` | +| `T` | Date/time separator | `T` | +| `HH` | Hours in 24-hour format (00–23) | `14` | +| `mm` | Minutes (00–59) | `30` | +| `ss` | Seconds (00–59) | `00` | +| `.SSS` | Milliseconds (optional) | `.000` | +| `Z` | UTC timezone indicator | `Z` | + +## Examples + +A resource creation timestamp: + +```json +{ + "createdAt": "2025-01-15T14:30:00.000Z" +} +``` + +Filtering by date range: + +```bash +?filters=and(createdAt[gte]=2025-01-01T00:00:00.000Z,createdAt[lte]=2025-01-31T23:59:59.999Z) +``` diff --git a/guides/principles/filtering.mdx b/guides/principles/filtering.mdx index d2cb24a..fbef3b1 100644 --- a/guides/principles/filtering.mdx +++ b/guides/principles/filtering.mdx @@ -17,22 +17,26 @@ Filters follow the format `field[operator]=value`: ?filters=field[operator]=value ``` -Combine multiple filters with a semicolon (`;`). Group conditions with logical operators `and(...)` and `or(...)`, separating conditions inside with commas. +Combine multiple filters with a semicolon (`;`) — groups separated by `;` are implicitly AND-ed. Group conditions with logical operators `and(...)` and `or(...)`, separating conditions inside with commas. + +Use the string `null` to filter for null values: `field[eq]=null`. ### Operators | Operator | Description | Example | | --- | --- | --- | | `eq` | Equals | `id[eq]=abc-123` | +| `ne` | Not equals | `status[ne]=CLOSED` | | `gt` | Greater than | `amount.value[gt]=1000` | | `gte` | Greater than or equal | `createdAt[gte]=2025-01-01T00:00:00Z` | | `lt` | Less than | `amount.value[lt]=50000` | | `lte` | Less than or equal | `createdAt[lte]=2025-12-31T23:59:59Z` | | `in` | Matches any value in list (pipe-separated) | `type[in]=CHECKING\|SAVING` | -| `nin` | Excludes values in list | `status[nin]=CLOSED\|SUSPENDED` | -| `like` | Pattern matching | `name[like]=Trace%` | -| `last` | Last element of array equals (strings) | `states.status[last]=ACTIVE` | -| `last_ne` | Last element of array not equals (strings) | `states.status[last_ne]=CLOSED` | +| `nin` | Excludes values in list (pipe-separated) | `status[nin]=CLOSED\|SUSPENDED` | +| `like` | Case-insensitive partial match | `name[like]=trace` | +| `text` | Full-text search (case-insensitive) | `description[text]=payment` | +| `last` | Last element of array equals | `states.status[last]=ACTIVE` | +| `last_ne` | Last element of array not equals | `states.status[last_ne]=CLOSED` | ### Logical operators @@ -78,6 +82,6 @@ Full request example: ```bash curl --request GET \ - --url '{{sandboxUrl}}/api/v1/accounts?limit=10&filters=states.status[last]=ACTIVE' \ + --url '{{sandboxUrl}}/api/accounts?limit=10&filters=states.status[last]=ACTIVE' \ --header 'Authorization: Bearer ' ``` diff --git a/guides/principles/idempotency.mdx b/guides/principles/idempotency.mdx index 26c9400..6f7d407 100644 --- a/guides/principles/idempotency.mdx +++ b/guides/principles/idempotency.mdx @@ -15,7 +15,7 @@ Mutating endpoints (POST, PUT, PATCH) require the `X-Idempotency-Key` header. Th ```bash curl --request POST \ - --url {{sandboxUrl}}/api/v1/operations \ + --url {{sandboxUrl}}/api/operations \ --header 'Authorization: Bearer ' \ --header 'X-Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000' \ --header 'Content-Type: application/json' \ @@ -28,7 +28,7 @@ The `X-Idempotency-Key` header is required on any request that creates or modifi ### Conflict behavior -If a request arrives with an idempotency key that was already used for a **different** request body, the API returns HTTP `409 Conflict`: +If a request arrives with an idempotency key that was already used, the API returns HTTP `409 Conflict`: ```json { @@ -38,8 +38,6 @@ If a request arrives with an idempotency key that was already used for a **diffe } ``` -If the key matches a previous request with the **same** body, the API returns the original response without reprocessing. - ## Examples Generate a UUID v4 for each unique operation: diff --git a/guides/principles/money.mdx b/guides/principles/money.mdx index f98c158..e63bbf7 100644 --- a/guides/principles/money.mdx +++ b/guides/principles/money.mdx @@ -15,52 +15,32 @@ Every amount in the API uses the same structure: ```json { - "value": 500000, - "asset": "BRL" + "value": 11, + "decimalValue": "0.11", + "asset": "BRL", + "decimals": 2 } ``` | Field | Type | Description | | --- | --- | --- | | `value` | integer | Amount in the smallest currency unit (e.g. centavos for BRL, cents for USD) | +| `decimalValue` | string | Human-readable decimal representation of the amount | | `asset` | string | ISO 4217 currency code | +| `decimals` | integer | Number of decimal places for the currency | -### Converting to display values +### Examples by currency -Divide the `value` by the currency's minor unit factor (typically 100) to get the display amount: - -| API value | Asset | Display amount | -| --- | --- | --- | -| `500000` | `BRL` | R$ 5.000,00 | -| `1050` | `USD` | $10.50 | -| `100000000` | `BTC` | 1.00000000 BTC | +| `value` | `decimals` | `asset` | `decimalValue` | +| --- | --- | --- | --- | +| `500000` | `2` | `BRL` | `"5000.00"` | +| `1050` | `2` | `USD` | `"10.50"` | +| `100000000` | `8` | `BTC` | `"1.00000000"` | - Always use integer arithmetic when working with amounts. Never convert to floating-point for calculations. + Always use integer arithmetic when working with `value`. The `decimalValue` field is provided for display purposes only. -### Date and time format - -All dates and timestamps use **UTC** in ISO 8601 format: - -``` -yyyy-MM-ddTHH:mm:ss.SSSZ -``` - -Example: `2025-01-15T14:30:00.000Z` - -| Component | Description | Example | -| --- | --- | --- | -| `yyyy` | Four-digit year | `2025` | -| `MM` | Two-digit month (01–12) | `01` | -| `dd` | Two-digit day (01–31) | `15` | -| `T` | Date/time separator | `T` | -| `HH` | Hours in 24-hour format (00–23) | `14` | -| `mm` | Minutes (00–59) | `30` | -| `ss` | Seconds (00–59) | `00` | -| `.SSS` | Milliseconds (optional) | `.000` | -| `Z` | UTC timezone indicator | `Z` | - ## Examples A deposit of R$ 1.250,00: @@ -69,7 +49,9 @@ A deposit of R$ 1.250,00: { "amount": { "value": 125000, - "asset": "BRL" + "decimalValue": "1250.00", + "asset": "BRL", + "decimals": 2 } } ``` @@ -80,7 +62,9 @@ A withdrawal of $500.00: { "amount": { "value": 50000, - "asset": "USD" + "decimalValue": "500.00", + "asset": "USD", + "decimals": 2 } } ``` diff --git a/guides/principles/pagination.mdx b/guides/principles/pagination.mdx index 9187661..e0e6ade 100644 --- a/guides/principles/pagination.mdx +++ b/guides/principles/pagination.mdx @@ -13,8 +13,11 @@ List endpoints return paginated responses using cursor-based pagination. This ap | Parameter | Type | Default | Description | | --- | --- | --- | --- | -| `limit` | integer | `10` | Number of items per page | +| `limit` | integer | `10` | Maximum number of items to return per page | | `cursor` | string | — | Cursor returned from a previous response | +| `direction` | string | — | Pagination direction: `NEXT` or `PREVIOUS` | +| `sortOrder` | string | `DESCENDING` | Sort direction: `ASCENDING` or `DESCENDING` | +| `includeTotalMatches` | boolean | `false` | Whether to include the total matching count in the response | ### Response structure @@ -39,8 +42,8 @@ Every list response includes a `meta` object alongside the `data` array: | --- | --- | | `previousCursor` | Cursor to fetch the previous page. `null` on the first page. | | `nextCursor` | Cursor to fetch the next page. `null` on the last page. | -| `total` | Total number of items in the collection. | -| `totalMatches` | Total items matching the current filters. | +| `total` | Number of items returned in the current page. | +| `totalMatches` | Total items matching the current filters. Only present when `includeTotalMatches=true`. | ## Examples @@ -48,7 +51,7 @@ Every list response includes a `meta` object alongside the `data` array: ```bash curl --request GET \ - --url '{{sandboxUrl}}/api/v1/accounts?limit=10' \ + --url '{{sandboxUrl}}/api/accounts?limit=10' \ --header 'Authorization: Bearer ' ``` @@ -56,7 +59,7 @@ curl --request GET \ ```bash curl --request GET \ - --url '{{sandboxUrl}}/api/v1/accounts?limit=10&cursor=eyJpZCI6ImFjY18wMDIifQ' \ + --url '{{sandboxUrl}}/api/accounts?limit=10&cursor=eyJpZCI6ImFjY18wMDIifQ' \ --header 'Authorization: Bearer ' ``` @@ -70,7 +73,7 @@ while True: if cursor: params["cursor"] = cursor - response = client.get("/api/v1/accounts", params=params) + response = client.get("/api/accounts", params=params) data = response.json() for item in data["data"]: diff --git a/guides/principles/versioning.mdx b/guides/principles/versioning.mdx index 6cf41aa..d18ae34 100644 --- a/guides/principles/versioning.mdx +++ b/guides/principles/versioning.mdx @@ -15,7 +15,7 @@ Include the `X-Trace-Version` header in your requests: ```bash curl --request GET \ - --url {{sandboxUrl}}/api/v1/accounts \ + --url {{sandboxUrl}}/api/accounts \ --header 'Authorization: Bearer ' \ --header 'X-Trace-Version: 2' ``` @@ -65,6 +65,6 @@ No version header (uses default): ```bash curl --request GET \ - --url {{sandboxUrl}}/api/v1/accounts \ + --url {{sandboxUrl}}/api/accounts \ --header 'Authorization: Bearer ' ``` diff --git a/journeys/deposit.mdx b/journeys/deposit.mdx index 566d393..8054d38 100644 --- a/journeys/deposit.mdx +++ b/journeys/deposit.mdx @@ -26,7 +26,7 @@ Use this when you create the deposit intent before the funds arrive. ```bash curl --request POST \ - --url {{sandboxUrl}}/api/v1/operations \ + --url {{sandboxUrl}}/api/operations \ --header 'Authorization: Bearer ' \ --header 'X-Idempotency-Key: ' \ diff --git a/journeys/open-brl-account.mdx b/journeys/open-brl-account.mdx index 8e8cd65..e1338ad 100644 --- a/journeys/open-brl-account.mdx +++ b/journeys/open-brl-account.mdx @@ -24,7 +24,7 @@ This guide walks you through creating a BRL (Brazilian Real) account for an acco ```bash curl --request POST \ - --url {{sandboxUrl}}/api/v1/accounts \ + --url {{sandboxUrl}}/api/accounts \ --header 'Authorization: Bearer ' \ --header 'X-Idempotency-Key: ' \ diff --git a/journeys/open-crypto-account.mdx b/journeys/open-crypto-account.mdx index 4d8215f..49a27fc 100644 --- a/journeys/open-crypto-account.mdx +++ b/journeys/open-crypto-account.mdx @@ -24,7 +24,7 @@ This guide walks you through creating a crypto asset account for an account owne ```bash curl --request POST \ - --url {{sandboxUrl}}/api/v1/accounts \ + --url {{sandboxUrl}}/api/accounts \ --header 'Authorization: Bearer ' \ --header 'X-Idempotency-Key: ' \ diff --git a/journeys/swap.mdx b/journeys/swap.mdx index 89ff7c2..055bd89 100644 --- a/journeys/swap.mdx +++ b/journeys/swap.mdx @@ -24,7 +24,7 @@ Swaps convert funds between currencies within the platform — for example, BRL ```bash curl --request POST \ - --url {{sandboxUrl}}/api/v1/quotes \ + --url {{sandboxUrl}}/api/quotes \ --header 'Authorization: Bearer ' \ --header 'Content-Type: application/json' \ @@ -45,7 +45,7 @@ Swaps convert funds between currencies within the platform — for example, BRL ```bash curl --request POST \ - --url {{sandboxUrl}}/api/v1/operations \ + --url {{sandboxUrl}}/api/operations \ --header 'Authorization: Bearer ' \ --header 'X-Idempotency-Key: ' \ diff --git a/journeys/withdrawal.mdx b/journeys/withdrawal.mdx index 7f4387f..56c0624 100644 --- a/journeys/withdrawal.mdx +++ b/journeys/withdrawal.mdx @@ -25,7 +25,7 @@ Withdrawals move funds from an account to an external destination — a bank acc ```bash curl --request POST \ - --url {{sandboxUrl}}/api/v1/beneficiaries \ + --url {{sandboxUrl}}/api/beneficiaries \ --header 'Authorization: Bearer ' \ --header 'X-Idempotency-Key: ' \ @@ -41,7 +41,7 @@ Withdrawals move funds from an account to an external destination — a bank acc ```bash curl --request POST \ - --url {{sandboxUrl}}/api/v1/operations \ + --url {{sandboxUrl}}/api/operations \ --header 'Authorization: Bearer ' \ --header 'X-Idempotency-Key: ' \ diff --git a/quickstart.mdx b/quickstart.mdx index 2dcd6f4..c49c99c 100644 --- a/quickstart.mdx +++ b/quickstart.mdx @@ -23,7 +23,7 @@ import SandboxNote from '/snippets/sandbox-note.mdx'; ```bash curl --request GET \ - --url {{sandboxUrl}}/api/v1/accounts \ + --url {{sandboxUrl}}/api/accounts \ --header 'Authorization: Bearer YOUR_ACCESS_TOKEN' ``` diff --git a/webhooks/overview.mdx b/webhooks/overview.mdx index 8ed05c7..3ddfda2 100644 --- a/webhooks/overview.mdx +++ b/webhooks/overview.mdx @@ -16,7 +16,7 @@ Your webhook endpoint must: - Accept **POST** requests with a JSON body. - Be reachable over **HTTPS**. -- Return a **2xx** status code within **10 seconds** to acknowledge receipt. +- Return a **2xx** status code within **5 seconds** to acknowledge receipt. If your endpoint returns a non-2xx status or times out, Trace treats the delivery as failed and schedules a retry. @@ -96,17 +96,9 @@ Every webhook request includes an `X-Message-Signature` header containing an HMA ## Retry policy -If delivery fails, Trace retries with exponential backoff: +If delivery fails, Trace retries at a **constant interval of 5 minutes** for up to **24 hours** (288 attempts). After all retries are exhausted, the event is marked as failed permanently. -| Attempt | Delay | -| --- | --- | -| 1st retry | 1 minute | -| 2nd retry | 5 minutes | -| 3rd retry | 30 minutes | -| 4th retry | 2 hours | -| 5th retry | 24 hours | - -After all retries are exhausted, the event is marked as failed. Events are delivered **at least once** — your endpoint should be idempotent to handle potential duplicates. +Events are delivered **at least once** — your endpoint should be idempotent to handle potential duplicates. Use the `messageId` field to deduplicate events on your side. From fd6e25a20ddba794c76a958d70043d07f4e75aba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1rio=20Alvial?= Date: Tue, 14 Apr 2026 17:07:43 -0300 Subject: [PATCH 7/8] fix: resolve CI failures for markdown linting and orphan detection - Add language specifiers to fenced code blocks (MD040) - Unindent code blocks inside Tabs component (MD046) - Remove orphaned webhook pages from repo (removed from nav earlier) --- guides/principles/datetime.mdx | 2 +- guides/principles/filtering.mdx | 4 +- guides/principles/idempotency.mdx | 14 ++-- webhooks/events.mdx | 57 ---------------- webhooks/overview.mdx | 105 ------------------------------ 5 files changed, 10 insertions(+), 172 deletions(-) delete mode 100644 webhooks/events.mdx delete mode 100644 webhooks/overview.mdx diff --git a/guides/principles/datetime.mdx b/guides/principles/datetime.mdx index 4f212aa..5c82db9 100644 --- a/guides/principles/datetime.mdx +++ b/guides/principles/datetime.mdx @@ -11,7 +11,7 @@ All dates and timestamps in the Trace FX API use **UTC** in ISO 8601 format. No ### Format -``` +```text yyyy-MM-ddTHH:mm:ss.SSSZ ``` diff --git a/guides/principles/filtering.mdx b/guides/principles/filtering.mdx index fbef3b1..604b603 100644 --- a/guides/principles/filtering.mdx +++ b/guides/principles/filtering.mdx @@ -13,7 +13,7 @@ List endpoints support a `filters` query parameter that lets you narrow results Filters follow the format `field[operator]=value`: -``` +```text ?filters=field[operator]=value ``` @@ -42,7 +42,7 @@ Use the string `null` to filter for null values: `field[eq]=null`. Combine conditions using `and(...)` or `or(...)`: -``` +```text ?filters=and(amount.value[gt]=1000,amount.value[lt]=50000) ``` diff --git a/guides/principles/idempotency.mdx b/guides/principles/idempotency.mdx index 6f7d407..fb5d3db 100644 --- a/guides/principles/idempotency.mdx +++ b/guides/principles/idempotency.mdx @@ -44,16 +44,16 @@ Generate a UUID v4 for each unique operation: - ```python - import uuid +```python +import uuid - idempotency_key = str(uuid.uuid4()) - ``` +idempotency_key = str(uuid.uuid4()) +``` - ```javascript - const idempotencyKey = crypto.randomUUID(); - ``` +```javascript +const idempotencyKey = crypto.randomUUID(); +``` diff --git a/webhooks/events.mdx b/webhooks/events.mdx deleted file mode 100644 index 96b3f4d..0000000 --- a/webhooks/events.mdx +++ /dev/null @@ -1,57 +0,0 @@ ---- -title: "Event reference" -description: "Complete catalog of webhook events grouped by domain." -sidebarTitle: "Event reference" ---- - -## Overview - -Each webhook event has a `type` field that identifies what happened. Events are grouped by domain. - -All events share this structure: - -```json -{ - "messageId": "evt_550e8400-e29b-41d4-a716-446655440000", - "type": "account.activated", - "timestamp": "2025-01-15T14:30:00.000Z", - "data": { } -} -``` - -| Field | Description | -| --- | --- | -| `messageId` | Unique event identifier. Use for deduplication and signature verification. | -| `type` | Event type (see tables below). | -| `timestamp` | When the event occurred (ISO 8601 UTC). | -| `data` | Event-specific payload. | - -## Account events - -Triggered when account lifecycle changes occur. - -| Event type | Description | -| --- | --- | -| `account.created` | A new account was created. | -| `account.activated` | An account is now active and ready for use. | -| `account.deactivated` | An account was deactivated. | - -## Payment events - -Triggered when payment operations change state. - -| Event type | Description | -| --- | --- | -| `operation.created` | A new operation (deposit, withdrawal, or swap) was created. | -| `operation.completed` | An operation finished successfully. | -| `operation.failed` | An operation failed. Check `data` for the failure reason. | - -## Beneficiary events - -Triggered when beneficiary records change. - -| Event type | Description | -| --- | --- | -| `beneficiary.created` | A new beneficiary was registered. | -| `beneficiary.approved` | A beneficiary passed compliance review. | -| `beneficiary.rejected` | A beneficiary was rejected during compliance review. | diff --git a/webhooks/overview.mdx b/webhooks/overview.mdx deleted file mode 100644 index 3ddfda2..0000000 --- a/webhooks/overview.mdx +++ /dev/null @@ -1,105 +0,0 @@ ---- -title: "Webhooks overview" -description: "Receive real-time notifications when events occur on the Trace FX platform." -sidebarTitle: "Overview" ---- - -## Overview - -Webhooks notify your application in real time when events occur on the Trace FX platform — such as an account being activated, a payment completing, or a beneficiary being approved. Instead of polling the API, you register an endpoint and Trace pushes events to it. - -## Setup - -### Endpoint requirements - -Your webhook endpoint must: - -- Accept **POST** requests with a JSON body. -- Be reachable over **HTTPS**. -- Return a **2xx** status code within **5 seconds** to acknowledge receipt. - -If your endpoint returns a non-2xx status or times out, Trace treats the delivery as failed and schedules a retry. - -### Registering your endpoint - -Webhook endpoints are configured during onboarding. Contact your Trace integration manager to register or update your endpoint URL. - -## Signatures - -Every webhook request includes an `X-Message-Signature` header containing an HMAC-SHA256 hash. Use this to verify that the request came from Trace and was not tampered with. - -### Verification steps - -1. Extract the `messageId` from the request body. -2. Concatenate `{messageId}+{clientId}` — the `+` is a literal character, not string concatenation. -3. Compute the HMAC-SHA256 hash using your **client secret** as the key. -4. Compare the result with the `X-Message-Signature` header value. - -### Code examples - - - - ```python - import hashlib - import hmac - - def verify_signature(message_id, client_id, client_secret, received_signature): - payload = f"{message_id}+{client_id}" - computed = hmac.new( - client_secret.encode(), - payload.encode(), - hashlib.sha256 - ).hexdigest() - return hmac.compare_digest(computed, received_signature) - ``` - - - ```javascript - const crypto = require("crypto"); - - function verifySignature(messageId, clientId, clientSecret, receivedSignature) { - const payload = `${messageId}+${clientId}`; - const computed = crypto - .createHmac("sha256", clientSecret) - .update(payload) - .digest("hex"); - return crypto.timingSafeEqual( - Buffer.from(computed), - Buffer.from(receivedSignature) - ); - } - ``` - - - ```kotlin - import javax.crypto.Mac - import javax.crypto.spec.SecretKeySpec - - fun verifySignature( - messageId: String, clientId: String, - clientSecret: String, receivedSignature: String - ): Boolean { - val payload = "$messageId+$clientId" - val mac = Mac.getInstance("HmacSHA256") - mac.init(SecretKeySpec(clientSecret.toByteArray(), "HmacSHA256")) - val computed = mac.doFinal(payload.toByteArray()) - .joinToString("") { "%02x".format(it) } - return computed == receivedSignature - } - ``` - - - - - Always use constant-time comparison when verifying signatures to prevent timing attacks. - - -## Retry policy - -If delivery fails, Trace retries at a **constant interval of 5 minutes** for up to **24 hours** (288 attempts). After all retries are exhausted, the event is marked as failed permanently. - -Events are delivered **at least once** — your endpoint should be idempotent to handle potential duplicates. - - - Use the `messageId` field to deduplicate events on your side. - From e0d043976b506efdaa8998565b97485e1de40736 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1rio=20Alvial?= Date: Tue, 14 Apr 2026 17:10:52 -0300 Subject: [PATCH 8/8] fix: remove broken webhook links and fix markdown lint errors - Remove all /webhooks/ links from journey pages and home (pages deleted) - Add blank lines around fenced code blocks in Tabs (MD031) - Remove Webhooks card from home page --- guides/principles/idempotency.mdx | 4 ++++ index.mdx | 3 --- journeys/deposit.mdx | 4 ++-- journeys/open-brl-account.mdx | 3 +-- journeys/open-crypto-account.mdx | 2 +- journeys/swap.mdx | 3 +-- journeys/withdrawal.mdx | 5 ++--- 7 files changed, 11 insertions(+), 13 deletions(-) diff --git a/guides/principles/idempotency.mdx b/guides/principles/idempotency.mdx index fb5d3db..9494d29 100644 --- a/guides/principles/idempotency.mdx +++ b/guides/principles/idempotency.mdx @@ -44,16 +44,20 @@ Generate a UUID v4 for each unique operation: + ```python import uuid idempotency_key = str(uuid.uuid4()) ``` + + ```javascript const idempotencyKey = crypto.randomUUID(); ``` + diff --git a/index.mdx b/index.mdx index 0bb0fc2..f24584a 100644 --- a/index.mdx +++ b/index.mdx @@ -37,7 +37,4 @@ mode: "wide" Exchange between BRL, USD, and crypto assets. - - Real-time notifications for account and payment events. - diff --git a/journeys/deposit.mdx b/journeys/deposit.mdx index 8054d38..7b0d089 100644 --- a/journeys/deposit.mdx +++ b/journeys/deposit.mdx @@ -44,13 +44,13 @@ Use this when you create the deposit intent before the funds arrive. The operation is created in `PENDING` status. - Listen for `operation.created`, `operation.completed`, and `operation.failed` [webhook events](/webhooks/events) as the deposit progresses. + Listen for `operation.created`, `operation.completed`, and `operation.failed` webhook events as the deposit progresses. ### Money-first flow -When funds arrive at the account before an explicit deposit intent is created, the platform automatically creates the operation and notifies you via [webhook events](/webhooks/events). +When funds arrive at the account before an explicit deposit intent is created, the platform automatically creates the operation and notifies you via webhook events. ## What happens next diff --git a/journeys/open-brl-account.mdx b/journeys/open-brl-account.mdx index e1338ad..8dec341 100644 --- a/journeys/open-brl-account.mdx +++ b/journeys/open-brl-account.mdx @@ -38,7 +38,7 @@ This guide walks you through creating a BRL (Brazilian Real) account for an acco The account is returned in `PENDING` status. - The account goes through internal validation. Listen for the [`account.activated`](/webhooks/events) webhook event or poll the account endpoint until the status changes to `ACTIVE`. + The account goes through internal validation. Listen for the `account.activated` webhook event or poll the account endpoint until the status changes to `ACTIVE`. Once the account is active, retrieve its banking details (branch, account number) to share with the account owner for incoming transfers. @@ -48,4 +48,3 @@ This guide walks you through creating a BRL (Brazilian Real) account for an acco ## What happens next - [Make a deposit](/journeys/deposit) — fund the account via PIX or bank transfer. -- [Webhooks](/webhooks/overview) — set up real-time notifications for account events. diff --git a/journeys/open-crypto-account.mdx b/journeys/open-crypto-account.mdx index 49a27fc..10d7eb1 100644 --- a/journeys/open-crypto-account.mdx +++ b/journeys/open-crypto-account.mdx @@ -38,7 +38,7 @@ This guide walks you through creating a crypto asset account for an account owne The account is returned in `PENDING` status. - The account goes through compliance checks. Listen for the [`account.activated`](/webhooks/events) webhook event or poll the account endpoint until the status changes to `ACTIVE`. + The account goes through compliance checks. Listen for the `account.activated` webhook event or poll the account endpoint until the status changes to `ACTIVE`. Once active, retrieve the account details to obtain the wallet address for receiving crypto deposits. diff --git a/journeys/swap.mdx b/journeys/swap.mdx index 055bd89..8a9cfad 100644 --- a/journeys/swap.mdx +++ b/journeys/swap.mdx @@ -58,7 +58,7 @@ Swaps convert funds between currencies within the platform — for example, BRL ``` - Listen for `operation.created`, `operation.completed`, and `operation.failed` [webhook events](/webhooks/events). + Listen for `operation.created`, `operation.completed`, and `operation.failed` webhook events. @@ -69,4 +69,3 @@ Swaps convert funds between currencies within the platform — for example, BRL ## What happens next - [Make a withdrawal](/journeys/withdrawal) — send the converted funds to an external destination. -- [Webhooks](/webhooks/overview) — monitor swap events in real time. diff --git a/journeys/withdrawal.mdx b/journeys/withdrawal.mdx index 56c0624..b047187 100644 --- a/journeys/withdrawal.mdx +++ b/journeys/withdrawal.mdx @@ -36,7 +36,7 @@ Withdrawals move funds from an account to an external destination — a bank acc }' ``` - Wait for the [`beneficiary.approved`](/webhooks/events) webhook event before proceeding. The beneficiary may be rejected during compliance review. + Wait for the `beneficiary.approved` webhook event before proceeding. The beneficiary may be rejected during compliance review. ```bash @@ -58,11 +58,10 @@ Withdrawals move funds from an account to an external destination — a bank acc ``` - The withdrawal may go through compliance review. Listen for `operation.created`, `operation.completed`, and `operation.failed` [webhook events](/webhooks/events). + The withdrawal may go through compliance review. Listen for `operation.created`, `operation.completed`, and `operation.failed` webhook events. ## What happens next - [Execute a swap](/journeys/swap) — convert currencies before withdrawing. -- [Webhooks](/webhooks/overview) — monitor all operation events.