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

# Begin WebAuthn Authentication

> This endpoint allows users to get a challenge for a passkey or security key proof.

```http theme={null}
POST /api/v1/account/2fa/webauthn/authenticate/begin
```

Returns a WebAuthn challenge for a passkey or security key [proof](https://docs.pacifica.fi/api-documentation/api/rest-api/two-factor#proofs). The challenge is bound to one request: a withdrawal or a factor change.

Pacifica accepts passkeys only from the Pacifica app. A script or bot cannot make a passkey proof. API clients should use a TOTP proof. See [Passkeys and security keys](https://docs.pacifica.fi/api-documentation/api/rest-api/two-factor#passkeys-and-security-keys).

#### Operation Type (for Signing)

| Header Field | Type | Content |
| - | - | - |
| `"type"` | string | `"begin_webauthn_authentication"` |

#### Request Body

<table><thead><tr><th width="176">Field</th><th width="98">Type</th><th width="95">Need</th><th>Description</th><th>Example</th></tr></thead><tbody><tr><td>`"account"`</td><td>string</td><td>required</td><td>User's wallet address</td><td>`42trU9A5...`</td></tr><tr><td>`"signature"`</td><td>string</td><td>required</td><td>Cryptographic signature</td><td>`5j1Vy9Uq...`</td></tr><tr><td>`"timestamp"`</td><td>integer</td><td>required</td><td>Current timestamp in milliseconds</td><td>`1716200000000`</td></tr><tr><td>`"action"`</td><td>string</td><td>required</td><td>What the proof will authorize:\
`"withdrawal"`\
`"factor_management"`</td><td>`withdrawal`</td></tr><tr><td>`"intent_hash"`</td><td>string</td><td>required</td><td>Hash of the request that the proof will authorize. Base64url, no padding, 32 bytes. See below</td><td>`q1Xn0bJr8m2p...`</td></tr><tr><td>`"kind"`</td><td>string</td><td>required</td><td>Factor kind to use:\
`"passkey"`\
`"security_key"`</td><td>`passkey`</td></tr><tr><td>`"agent_wallet"`</td><td>string</td><td>optional</td><td>Agent wallet address</td><td>`69trU9A5...`</td></tr><tr><td>`"expiry_window"`</td><td>integer</td><td>optional</td><td>Signature expiry in milliseconds</td><td>`30000`</td></tr></tbody></table>

```json theme={null}
{
  "account": "42trU9A5...",
  "signature": "5j1Vy9Uq...",
  "timestamp": 1716200000000,
  "action": "withdrawal",
  "intent_hash": "q1Xn0bJr8m2p...",
  "kind": "passkey",
  "expiry_window": 30000
}
```

You can sign this request with the account key or with an [agent wallet](https://docs.pacifica.fi/api-documentation/api/signing/api-agent-keys.md). The account must have 2FA on.

`intent_hash` covers the action, the account, and the signed `type` and `data` of the request that the proof will authorize. It does not cover `timestamp` or `expiry_window`. The Pacifica app computes `intent_hash` for you.

To use the challenge:

1. Pass `options` to `navigator.credentials.get()` in the Pacifica app.
2. Send the request that `intent_hash` describes, with this proof in `two_factor` or `proof`:

```json theme={null}
{
  "type": "webauthn",
  "challenge_id": "45c48cce-2e2d-4fbd-8f3b-7a1c9e0d5b62",
  "credential": {
    "id": "pQx3...",
    "rawId": "pQx3...",
    "type": "public-key",
    "response": {
      "authenticatorData": "SZYN5YgO...",
      "clientDataJSON": "eyJ0eXBl...",
      "signature": "MEUCIQDx...",
      "userHandle": "rT9b..."
    }
  }
}
```

`credential` is the assertion from `navigator.credentials.get()` as JSON, with binary values as base64url strings.

A challenge lasts 5 minutes and works once. If the proof arrives with a different request, Pacifica rejects it and the challenge is used up.

#### Signing

Sign `action`, `intent_hash`, and `kind` as the `data` object. For the request above, the message to sign is:

```json theme={null}
{"data":{"action":"withdrawal","intent_hash":"q1Xn0bJr8m2p...","kind":"passkey"},"expiry_window":30000,"timestamp":1716200000000,"type":"begin_webauthn_authentication"}
```

#### Response

* Status 200: Challenge created

```json theme={null}
{
  "success": true,
  "data": {
    "challenge_id": "45c48cce-2e2d-4fbd-8f3b-7a1c9e0d5b62",
    "options": {
      "publicKey": {
        "challenge": "x8Jf2KqLm0pR..."
      }
    }
  },
  "error": null,
  "code": null
}
```

The `options` above are shortened.

| Field | Type | Description |
| - | - | - |
| `'challenge_id'` | string | Challenge ID (UUID). Send it in the `webauthn` proof within 5 minutes. It works once |
| `'options'` | object | WebAuthn request options for `navigator.credentials.get()`. Binary values are base64url strings |

* Status 400: Invalid request parameters, `intent_hash`, or signature
* Status 403: The account does not exist
* Status 422: The account cannot make this proof. The `error` field says why:

| `error` | Meaning |
| - | - |
| `two-factor is not enabled` | 2FA is off |
| `no matching WebAuthn credential` | The account has no factor of this `kind` |
| `set up two-factor authentication before continuing` | The account has never started an enrollment |

* Status 429: More than 60 challenges in 10 minutes for this action and signer
* Status 500: Internal server error
* Status 503: The two-factor service is unavailable. Retry later

#### Code Example (Python)

```python theme={null}
import requests

payload = {
    "account": "42trU9A5...",
    "signature": "5j1Vy9Uq...",
    "timestamp": 1716200000000,
    "action": "withdrawal",
    "intent_hash": "q1Xn0bJr8m2p...",
    "kind": "passkey"
}

response = requests.post(
    "/api/v1/account/2fa/webauthn/authenticate/begin",
    json=payload,
    headers={"Content-Type": "application/json"}
)

data = response.json()
```


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