> ## Documentation Index
> Fetch the complete documentation index at: https://pay3.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Sign In by Verifying Your Stellar Wallet Signature

> Submit a signed challenge to complete wallet-based sign-in. On success, Pay3 sets a session cookie and returns your authenticated user context.

After you've received a challenge from [`POST /api/auth/challenge`](https://pay3.mintlify.site/api-reference/auth/challenge) and signed the `message` field with your Stellar wallet, submit the signature here to complete sign-in. Pay3 verifies the signature against the challenge, marks it as consumed, and creates your session. For browser clients, an httpOnly `pay3_session` cookie is set automatically and sent with every subsequent request.

## Endpoint

**`POST /api/auth/verify`**

No authentication is required to call this endpoint.

***

## Request Body

**walletAddress** `string` *required*

The same Stellar public key (`G...`) you used when requesting the challenge.

**nonce** `string` *required*

The `nonce` string returned by `POST /api/auth/challenge`. Pay3 uses this
to look up the exact challenge record server-side.

**signature** `string` *required*

Your Ed25519 signature of the challenge `message`, encoded as **base64** (or
as a 128-character lowercase hex string). Pay3 accepts both raw Ed25519
signatures and SEP-53 Freighter-style signatures (where the message is
pre-hashed as `SHA256("Stellar Signed Message:\n" + message)` before signing).

***

## Response

All Pay3 API responses are wrapped in a standard envelope:

```json theme={null}
{
  "data": { ... },
  "requestId": "req_01hx2q3r4s5t6u7v8w9x0y1z"
}
```

A successful `200 OK` response confirms authentication and returns the new session context inside `data`. The session is established via the `pay3_session` httpOnly cookie set in the response headers — the session token is **not** present in the response body.

**data** `object`

The authenticated session context.

**data fields**

**data.userId** `string`

The Pay3 internal user ID associated with your wallet. Stable across sessions.

**data.walletId** `string`

The Pay3 internal wallet record ID for the authenticated Stellar address.

**data.walletAddress** `string`

Your Stellar public key, normalized and echoed back.

**data.network** `string`

The Stellar network for this session — `testnet` or `public`.

**data.expiresAt** `string`

ISO 8601 timestamp indicating when the session cookie expires.

**requestId** `string`

A unique identifier for this API request, useful for support and debugging.

> [!NOTE]
>
> The `pay3_session` cookie is **httpOnly** — it is not accessible to JavaScript
> and is sent automatically by the browser on all subsequent requests to
> `api.pay3.dev`. For server-to-server or CLI usage, read the `Set-Cookie`
> header from the verify response and pass the session token value via the
> `Authorization: Bearer <session-token>` header on protected endpoints.

***

## Signature Formats

Pay3 supports two signature formats, detected automatically:

| Format | Description |
| - | - |
| **Base64 (64 bytes decoded)** | Standard Ed25519 signature — produced by most programmatic signers and the Stellar SDK. |
| **Hex (128 lowercase chars)** | Alternative encoding of the same 64-byte Ed25519 signature. |

Freighter's `signMessage()` produces a SEP-53 signature (hashed before signing). Pay3 verifies both the raw and SEP-53 variants transparently — you do not need to pre-hash the message yourself.

***

## Example

### Request

```bash theme={null}
curl --request POST \
  --url https://api.pay3.dev/api/auth/verify \
  --header 'Content-Type: application/json' \
  --data '{
    "walletAddress": "GAHJJJKMOKYE4RVPZEWZTKH5FVI4PA3VL7GK2LFNUBSGBV8XVMHXV6R",
    "nonce": "8f3a92b1c4e07d56af2190be",
    "signature": "base64encodedSignatureHere=="
  }'
```

### Response Body

```json theme={null}
{
  "data": {
    "userId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "walletId": "f0e1d2c3-b4a5-6789-0123-456789abcdef",
    "walletAddress": "GAHJJJKMOKYE4RVPZEWZTKH5FVI4PA3VL7GK2LFNUBSGBV8XVMHXV6R",
    "network": "testnet",
    "expiresAt": "2024-11-08T12:00:00.000Z"
  },
  "requestId": "req_01hx2q3r4s5t6u7v8w9x0y1z"
}
```

***

## Error Responses

| HTTP Status | Error Code | Description |
| - | - | - |
| `400 Bad Request` | `CHALLENGE_NOT_FOUND` | No challenge found for the supplied nonce. |
| `400 Bad Request` | `CHALLENGE_WALLET_MISMATCH` | The wallet address doesn't match the one the challenge was issued for. |
| `400 Bad Request` | `CHALLENGE_CONSUMED` | This challenge has already been used to sign in. |
| `400 Bad Request` | `CHALLENGE_EXPIRED` | The 5-minute challenge window has passed — request a new one. |
| `401 Unauthorized` | `INVALID_SIGNATURE` | The signature could not be verified against the challenge message. |


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.