> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.latitude.xyz/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.latitude.xyz/_mcp/server.

# API basics

> Authentication, idempotency, webhooks, and other fundamentals for working with the Latitude API

This page covers the shared fundamentals that apply across all Latitude API products.

## Base URL

All API requests are made to:

```
https://api.latitude.xyz/v1
```

## Authentication

Latitude uses HMAC-SHA256 signature-based authentication. All API requests must include a properly formatted `Authorization` header containing the signature and metadata.

### Required Headers

| Header             | Description                                                                                                  |
| ------------------ | ------------------------------------------------------------------------------------------------------------ |
| `Authorization`    | HMAC-SHA256 Credential=Credential,SignedHeaders=SignedHeaders,Signature=Signature                            |
| `Host`             | Latitude API host                                                                                            |
| `X-API-Key`        | The API key associated to the customer account                                                               |
| `X-Date`           | The current UTC time in ISO 8601 format. Example: `2025-09-16T12:07:15-05:00.000Z`                           |
| `X-Content-SHA256` | SHA256 hash of the request body                                                                              |
| `X-Nonce`          | We recommend using a unix timestamp concatenated with a 6-digit random value. Example: `1757865235000892731` |

### Authorization Header Components

| Component       | Description                                                                                                                        |
| --------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
| `Credential`    | The API key associated to the customer account                                                                                     |
| `SignedHeaders` | A list of headers used to create the signature, delimited by semicolons. Example: `host;x-api-key;x-date;x-content-sha256;x-nonce` |
| `Signature`     | The Base64-encoded signature of the canonical request payload                                                                      |

### Example: Properly Signed Request

**Request:**

```http
POST /v1/individuals HTTP/1.1

{
  "product_types": ["global_payouts"],
  "given_name": "Rahul",
  "family_name": "Patel",
  "country": "IN",
  "email": "rahul.patel@example.com",
  "phone": "+919876543210",
  "address": {
    "line_1": "Flat 304, Building B, Sunshine Apartments",
    "city": "Bengaluru",
    "state": "Karnataka",
    "postal_code": "560001"
  },
  "financial_accounts": [
    {
      "type": "upi",
      "details": {
        "upi_id": "9876543210@paytm"
      }
    }
  ]
}
```

**Expected Headers:**

```http
Authorization: HMAC-SHA256 Credential=key_gdrbD2lQAO8pkxiOIM7vFZes3HN38yoW,SignedHeaders=host;x-api-key;x-date;x-content-sha256;x-nonce,Signature=YTc5YjU1Y2YxYzZkNDQ4ZTU1ODRkYzY4MzE1YWRhODRjODNiNzZmMGYzYjhjMDdmYmVhMGQ4YTE0MzZjOGUwYg==
X-API-Key: key_gdrbD2lQAO8pkxiOIM7vFZes3HN38yoW
X-Date: 2025-09-16T12:07:15-05:00.000Z
X-Content-SHA256: 7a8b9c0d1e2f3g4h5i6j7k8l9m0n1o2p3q4r5s6t7u8v9w0x1y2z3a4b5c6d7e8f
X-Nonce: 1757865235000892731
```

### Canonical Request Payload

A plaintext representation of the request, consisting of the following parts joined by newlines:

1. Request method
2. Request path
3. Query string — **omitted entirely** (no line, not a blank line) when the request has no query string
4. Formatted headers (`name:value`, lowercased names), sorted by name, one per line
5. A sorted list of signed header names, delimited by semicolons
6. The SHA256 hash of the request payload

There are no blank separator lines between the parts.

**Example** (a `POST` with no query string, so the query string line is absent):

```
POST
/v1/individuals
host:api.latitude.xyz
x-api-key:key_gdrbD2lQAO8pkxiOIM7vFZes3HN38yoW
x-content-sha256:7a8b9c0d1e2f3g4h5i6j7k8l9m0n1o2p3q4r5s6t7u8v9w0x1y2z3a4b5c6d7e8f
x-date:2025-09-16T12:07:15-05:00.000Z
x-nonce:1757865235000892731
host;x-api-key;x-content-sha256;x-date;x-nonce
7a8b9c0d1e2f3g4h5i6j7k8l9m0n1o2p3q4r5s6t7u8v9w0x1y2z3a4b5c6d7e8f
```

### Signature Calculation

The final signature is generated by:

1. Producing a SHA256 hash of the canonical request payload
2. Adding the resulting hash to the final payload, and producing a HMAC-SHA256 signature using the secret key
3. Encoding the final signature using Base64

**Final Payload:**

```
HMAC-SHA256
2025-09-16T12:07:15-05:00.000Z
0d3b565633fe54a0850fa3bbcc1c108993297d63890766dc18e821bd924fbf6d
```

**Final Signature:**

```
a79b55cf1c6d448e5584dc68315ada84c83b76f0f3b8c07fbea0d8a1436c8e0b
```

**Base64 Encoded Signature:**

```
YTc5YjU1Y2YxYzZkNDQ4ZTU1ODRkYzY4MzE1YWRhODRjODNiNzZmMGYzYjhjMDdmYmVhMGQ4YTE0MzZjOGUwYg==
```

## Idempotency

The Transfers (`POST /v1/transfers`) and Conversion Accounts (`POST /v1/conversion_accounts`) endpoints support idempotency. This allows you to safely retry requests without accidentally performing the same operation twice. To use it, include an `Idempotency-Key` header with a UUID value. We recommend a V4 UUID to avoid collisions.

```bash
curl -v --location https://api.latitude.xyz/v1/transfers \
  --header 'Idempotency-Key: b3a7c700-3950-420b-8ecf-a1ca5d0afcbb' \
  --header 'Content-Type: application/json' \
  --data-raw '{
    "source_amount": "100.00",
    "source_currency": "usd",
    "destination_currency": "php",
    "financial_account": { "id": "fa_3UoI7VvsajLrPo1A" },
    "payout_method": "instapay"
  }'
```

When you supply a valid `Idempotency-Key`, we save the resulting status code and body and return it for subsequent requests with the same key. When we return a saved result, we include an `Idempotency-Replayed: true` header in the response. Successful responses (`200` and `201`) are cached; error responses are not, so a request that failed can be retried with the same key.

If the same key is sent with a different request body, the API will return a `409 Conflict` error. A key that is not a valid 36-character UUID is rejected with a `400 Bad Request`.

> **Note:** Cached responses expire after 24 hours. We recommend generating a new UUID for each unique operation and storing it alongside your internal records so retries use the same key.

## Webhooks

Subscribe to webhook events to get real-time notifications about transfer status changes, conversion updates, and KYC results. This is the recommended way to track the lifecycle of your payments.

To create a subscription, send a `POST` request to `/v1/webhook_subscriptions` with the URL where you'd like to receive events and the event types you're interested in.

**Request:**

```http
POST /v1/webhook_subscriptions HTTP/1.1
Content-Type: application/json

{
  "url": "https://your-domain.com/webhooks/latitude",
  "event_types": [
    "transfer.completed",
    "transfer.failed",
    "individual.status_changed",
    "conversion.created",
    "conversion.completed"
  ]
}
```

### Available Event Types

| Event Type                    | Description                                                 |
| ----------------------------- | ----------------------------------------------------------- |
| `transfer.created`            | A transfer was created and is awaiting funding              |
| `transfer.funded`             | A transfer was funded and execution has started             |
| `transfer.payout_initiated`   | A transfer's payout leg was initiated                       |
| `transfer.completed`          | A transfer completed successfully                           |
| `transfer.failed`             | A transfer terminally failed                                |
| `transfer.expired`            | A transfer's funding deadline passed before it was funded   |
| `conversion.created`          | A deposit was received into a virtual account               |
| `conversion.payout_initiated` | A blockchain transfer was initiated to a destination wallet |
| `conversion.completed`        | A blockchain transfer was completed to a destination wallet |
| `conversion.failed`           | A conversion terminally failed                              |
| `individual.status_changed`   | An individual's status changed (e.g. KYC result)            |

> **Note:** Webhook subscriptions are created at the organization level, not per-individual or per-transfer. Once a subscription is active, you will receive events for all individuals, transfers, and conversions across your account.

### Event Type Payload Examples

Every webhook is delivered as a `POST` request with a JSON body of the form below. The `event_type` field identifies the event, and `event_payload` contains the resource snapshot at the time the event fired.

```json
{
  "event_type": "transfer.completed",
  "event_payload": { ... }
}
```

The shape of `event_payload` depends on the resource the event relates to: transfer events carry a transfer object, conversion events carry a conversion object, and `individual.status_changed` carries an individual status object. Realistic examples for each event type follow.

#### `transfer.created`

Fired when a transfer is created. For transfers funded via a crypto deposit, the payload includes `deposit_instructions` describing where to send funds.

```json
{
  "event_type": "transfer.created",
  "event_payload": {
    "id": "tr_01h3x5n6z7y8w9v0u1t2s3r4",
    "client_reference_id": "order-4815",
    "source_amount": "100.00",
    "source_currency": "usdc",
    "destination_amount": "5640.15",
    "destination_currency": "php",
    "exchange_rate": "57.0",
    "recipient_id": "rec_01h3x5n6z7y8w9v0u1t2s3r4",
    "financial_account_id": "fa_01h3x5n6z7y8w9v0u1t2s3r4",
    "payout_method": "instapay",
    "status": "awaiting_funds",
    "total_fee": "1.05",
    "fee_currency": "usdc",
    "total_tax": "0.00",
    "tax_currency": "usdc",
    "net_source_amount": "98.95",
    "completed_in": null,
    "created_at": "2025-01-15T10:30:00Z",
    "updated_at": "2025-01-15T10:30:00Z",
    "failure_reason": null,
    "settlement_reference_id": null,
    "deposit_instructions": {
      "type": "crypto_wallet",
      "currency": "usdc",
      "payment_methods": ["base"],
      "details": {
        "address": "0x2f1a3c9b7e4d5a6f8c0b1d2e3f4a5b6c7d8e9f01"
      },
      "deadline": "2025-01-15T10:33:00Z"
    }
  }
}
```

#### `transfer.funded`

Fired once funds are received and the transfer begins processing.

```json
{
  "event_type": "transfer.funded",
  "event_payload": {
    "id": "tr_01h3x5n6z7y8w9v0u1t2s3r4",
    "client_reference_id": "order-4815",
    "source_amount": "100.00",
    "source_currency": "usdc",
    "destination_amount": "5640.15",
    "destination_currency": "php",
    "exchange_rate": "57.0",
    "recipient_id": "rec_01h3x5n6z7y8w9v0u1t2s3r4",
    "financial_account_id": "fa_01h3x5n6z7y8w9v0u1t2s3r4",
    "payout_method": "instapay",
    "status": "started",
    "total_fee": "1.05",
    "fee_currency": "usdc",
    "total_tax": "0.00",
    "tax_currency": "usdc",
    "net_source_amount": "98.95",
    "completed_in": null,
    "created_at": "2025-01-15T10:30:00Z",
    "updated_at": "2025-01-15T10:31:12Z",
    "failure_reason": null,
    "settlement_reference_id": null,
    "deposit_instructions": {
      "type": "crypto_wallet",
      "currency": "usdc",
      "payment_methods": ["base"],
      "details": {
        "address": "0x2f1a3c9b7e4d5a6f8c0b1d2e3f4a5b6c7d8e9f01"
      },
      "deadline": "2025-01-15T10:33:00Z"
    }
  }
}
```

#### `transfer.completed`

Fired when the payout settles successfully. `completed_in` (seconds) and `settlement_reference_id` are populated.

```json
{
  "event_type": "transfer.completed",
  "event_payload": {
    "id": "tr_01h3x5n6z7y8w9v0u1t2s3r4",
    "client_reference_id": "order-4815",
    "source_amount": "100.00",
    "source_currency": "usdc",
    "destination_amount": "5640.15",
    "destination_currency": "php",
    "exchange_rate": "57.0",
    "recipient_id": "rec_01h3x5n6z7y8w9v0u1t2s3r4",
    "financial_account_id": "fa_01h3x5n6z7y8w9v0u1t2s3r4",
    "payout_method": "instapay",
    "status": "completed",
    "total_fee": "1.05",
    "fee_currency": "usdc",
    "total_tax": "0.00",
    "tax_currency": "usdc",
    "net_source_amount": "98.95",
    "completed_in": "42",
    "created_at": "2025-01-15T10:30:00Z",
    "updated_at": "2025-01-15T10:31:54Z",
    "failure_reason": null,
    "settlement_reference_id": "INSTAPAY-2025-0115-8A3F2C",
    "deposit_instructions": {
      "type": "crypto_wallet",
      "currency": "usdc",
      "payment_methods": ["base"],
      "details": {
        "address": "0x2f1a3c9b7e4d5a6f8c0b1d2e3f4a5b6c7d8e9f01"
      },
      "deadline": "2025-01-15T10:33:00Z"
    }
  }
}
```

#### `transfer.failed`

Fired when a transfer terminally fails during processing. `failure_reason` describes the cause.

```json
{
  "event_type": "transfer.failed",
  "event_payload": {
    "id": "tr_01h3x5n6z7y8w9v0u1t2s3r4",
    "client_reference_id": "order-4815",
    "source_amount": "100.00",
    "source_currency": "usdc",
    "destination_amount": "5640.15",
    "destination_currency": "php",
    "exchange_rate": "57.0",
    "recipient_id": "rec_01h3x5n6z7y8w9v0u1t2s3r4",
    "financial_account_id": "fa_01h3x5n6z7y8w9v0u1t2s3r4",
    "payout_method": "instapay",
    "status": "failed",
    "total_fee": "1.05",
    "fee_currency": "usdc",
    "total_tax": "0.00",
    "tax_currency": "usdc",
    "net_source_amount": "98.95",
    "completed_in": null,
    "created_at": "2025-01-15T10:30:00Z",
    "updated_at": "2025-01-15T10:32:08Z",
    "failure_reason": "Recipient bank account could not be credited",
    "settlement_reference_id": null,
    "deposit_instructions": {
      "type": "crypto_wallet",
      "currency": "usdc",
      "payment_methods": ["base"],
      "details": {
        "address": "0x2f1a3c9b7e4d5a6f8c0b1d2e3f4a5b6c7d8e9f01"
      },
      "deadline": "2025-01-15T10:33:00Z"
    }
  }
}
```

#### `transfer.expired`

Fired when a transfer's funding deadline passes before funds are received. The transfer never started processing.

```json
{
  "event_type": "transfer.expired",
  "event_payload": {
    "id": "tr_01h3x5n6z7y8w9v0u1t2s3r4",
    "client_reference_id": "order-4815",
    "source_amount": "100.00",
    "source_currency": "usdc",
    "destination_amount": "5640.15",
    "destination_currency": "php",
    "exchange_rate": "57.0",
    "recipient_id": "rec_01h3x5n6z7y8w9v0u1t2s3r4",
    "financial_account_id": "fa_01h3x5n6z7y8w9v0u1t2s3r4",
    "payout_method": "instapay",
    "status": "expired",
    "total_fee": "1.05",
    "fee_currency": "usdc",
    "total_tax": "0.00",
    "tax_currency": "usdc",
    "net_source_amount": "98.95",
    "completed_in": null,
    "created_at": "2025-01-15T10:30:00Z",
    "updated_at": "2025-01-15T11:30:00Z",
    "failure_reason": null,
    "settlement_reference_id": null,
    "deposit_instructions": {
      "type": "crypto_wallet",
      "currency": "usdc",
      "payment_methods": ["base"],
      "details": {
        "address": "0x2f1a3c9b7e4d5a6f8c0b1d2e3f4a5b6c7d8e9f01"
      },
      "deadline": "2025-01-15T10:33:00Z"
    }
  }
}
```

#### `conversion.created`

Fired when a deposit is received into a conversion account's virtual account. The blockchain payout has not yet been initiated, so payout timestamps and `tx_hash` are `null`.

```json
{
  "event_type": "conversion.created",
  "event_payload": {
    "id": "cnv_01h9z8y7x6w5v4u3t2s1r0q9",
    "conversion_account_id": "cnva_01h3x5n6z7y8w9v0u1t2s3r4",
    "individual_id": "ind_01h3x5n6z7y8w9v0u1t2s3r4",
    "status": "pending",
    "deposit": {
      "amount": "1000.00",
      "currency": "usd",
      "received_at": "2025-01-15T09:00:00Z"
    },
    "payout": {
      "amount": "997.00",
      "currency": "usdc",
      "payout_account_id": "fa_01h3x5n6z7y8w9v0u1t2s3r4",
      "tx_hash": null,
      "initiated_at": null,
      "completed_at": null
    },
    "total_fee": "3.00",
    "fee_currency": "usd",
    "exchange_rate": "1.0",
    "created_at": "2025-01-15T09:00:00Z",
    "updated_at": "2025-01-15T09:00:00Z"
  }
}
```

#### `conversion.payout_initiated`

Fired when the blockchain payout to the destination wallet is broadcast. `initiated_at` and `tx_hash` are populated; `completed_at` is still `null`.

```json
{
  "event_type": "conversion.payout_initiated",
  "event_payload": {
    "id": "cnv_01h9z8y7x6w5v4u3t2s1r0q9",
    "conversion_account_id": "cnva_01h3x5n6z7y8w9v0u1t2s3r4",
    "individual_id": "ind_01h3x5n6z7y8w9v0u1t2s3r4",
    "status": "started",
    "deposit": {
      "amount": "1000.00",
      "currency": "usd",
      "received_at": "2025-01-15T09:00:00Z"
    },
    "payout": {
      "amount": "997.00",
      "currency": "usdc",
      "payout_account_id": "fa_01h3x5n6z7y8w9v0u1t2s3r4",
      "tx_hash": "0x8f2c1a9b3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8091a2b3c4d5e6f7a8b9c0d1e2f",
      "initiated_at": "2025-01-15T09:01:30Z",
      "completed_at": null
    },
    "total_fee": "3.00",
    "fee_currency": "usd",
    "exchange_rate": "1.0",
    "created_at": "2025-01-15T09:00:00Z",
    "updated_at": "2025-01-15T09:01:30Z"
  }
}
```

#### `conversion.completed`

Fired when the blockchain payout confirms. `completed_at` is populated.

```json
{
  "event_type": "conversion.completed",
  "event_payload": {
    "id": "cnv_01h9z8y7x6w5v4u3t2s1r0q9",
    "conversion_account_id": "cnva_01h3x5n6z7y8w9v0u1t2s3r4",
    "individual_id": "ind_01h3x5n6z7y8w9v0u1t2s3r4",
    "status": "completed",
    "deposit": {
      "amount": "1000.00",
      "currency": "usd",
      "received_at": "2025-01-15T09:00:00Z"
    },
    "payout": {
      "amount": "997.00",
      "currency": "usdc",
      "payout_account_id": "fa_01h3x5n6z7y8w9v0u1t2s3r4",
      "tx_hash": "0x8f2c1a9b3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8091a2b3c4d5e6f7a8b9c0d1e2f",
      "initiated_at": "2025-01-15T09:01:30Z",
      "completed_at": "2025-01-15T09:02:11Z"
    },
    "total_fee": "3.00",
    "fee_currency": "usd",
    "exchange_rate": "1.0",
    "created_at": "2025-01-15T09:00:00Z",
    "updated_at": "2025-01-15T09:02:11Z"
  }
}
```

#### `conversion.failed`

Fired when a conversion terminally fails.

```json
{
  "event_type": "conversion.failed",
  "event_payload": {
    "id": "cnv_01h9z8y7x6w5v4u3t2s1r0q9",
    "conversion_account_id": "cnva_01h3x5n6z7y8w9v0u1t2s3r4",
    "individual_id": "ind_01h3x5n6z7y8w9v0u1t2s3r4",
    "status": "failed",
    "deposit": {
      "amount": "1000.00",
      "currency": "usd",
      "received_at": "2025-01-15T09:00:00Z"
    },
    "payout": {
      "amount": "997.00",
      "currency": "usdc",
      "payout_account_id": "fa_01h3x5n6z7y8w9v0u1t2s3r4",
      "tx_hash": null,
      "initiated_at": null,
      "completed_at": null
    },
    "total_fee": "3.00",
    "fee_currency": "usd",
    "exchange_rate": "1.0",
    "created_at": "2025-01-15T09:00:00Z",
    "updated_at": "2025-01-15T09:03:45Z"
  }
}
```

#### `individual.status_changed`

Fired when an individual's status changes (for example, as a result of KYC review). `status_details` lists any action codes with human-readable messages. When there are no details, the `status_details` key is **omitted from the payload entirely** rather than being sent as an empty array or `null`.

```json
{
  "event_type": "individual.status_changed",
  "event_payload": {
    "id": "ind_01h3x5n6z7y8w9v0u1t2s3r4",
    "status": "action_required",
    "status_details": [
      {
        "code": "verification_image_blurry",
        "message": "Your ID photo was too blurry. Please provide a clearer photo."
      }
    ],
    "previous_status": "pending",
    "updated_at": "2025-01-15T12:00:00Z"
  }
}
```

Possible `status` values are `pending`, `action_required`, `under_review`, `active`, and `rejected`.

## Prefunded Flows

Currently all Latitude flows are prefunded. You send funds to Latitude and those funds settle before you can execute transfers. You can prefund using:

* **USD** via wire transfer to your Latitude-managed bank account
* **USDC** via blockchain deposit on the Base network

You can find the instructions for funding your account in the dashboard. We will support more flexible and real-time funding flows in the future.

## Sandbox Environment

Latitude provides a sandbox environment for testing your integration without moving real money. Contact us to get sandbox API credentials.

The sandbox supports magic strings for simulating different outcomes (e.g. KYC approval/rejection, transfer success/failure). See the individual integration guides for sandbox-specific details.