---
title: "Bind a signing key to a multisig wallet"
method: POST
path: "/v1/multisig-wallets/{walletId}/signers"
tags: ["Multisig Wallets"]
---

# Bind a signing key to a multisig wallet

`POST /v1/multisig-wallets/{walletId}/signers`

## Request

- Base URL: `https://api.prod.keyban.io`
- URL: `https://api.prod.keyban.io/v1/multisig-wallets/{walletId}/signers`
- Auth: one of:
  - API key in header `X-Api-Key`
  - `bearer` (a scheme the document does not define)

Enroll the authenticated admin as a signer, with either the address of a secp256k1 key or the P-256 public key of a passkey their signer app just created. Nothing happens on-chain yet: the enrollment waits for a peer validation (`requested` → `validated`), except for the wallet creator, who is validated on the spot since no peer can vouch for the first signer. Reaching the threshold in `validated` signers triggers the deployment; on an already-deployed wallet, a validation queues an operator `addSigners` instead. The address is withheld from reads until the enrollment is validated.

## Path parameters

- `walletId` string, uuid, required

## Request body

- EnrollMultisigSignerDto
  - `address` string: EVM address of a secp256k1 signing key held by the signer app — the ERC-7913 signer as-is (20 bytes). Exactly one of `address` and `publicKey` must be provided.
  - `publicKey` string: DER SubjectPublicKeyInfo (91 bytes) of a P-256 **passkey**, as returned by `getPublicKey()` at registration (require `getPublicKeyAlgorithm() === -7`). The server extracts the curve point, validates it, and composes the 84-byte ERC-7913 signer with the network's WebAuthn verifier — the signer bytes are never taken from the wire. Exactly one of `address` and `publicKey` must be provided.
  - `commitment` string, required: SAS engagement: `HMAC(nonceC ‖ signerBytes)`, 32 bytes. Binds the candidate's secret nonce before the validator's nonce is known, so the short code cannot be ground to a chosen value. The nonce itself is revealed later, never at enrollment. For a passkey, the app must commit over the composed 84-byte signer bytes.

## Response `202`

Enrollment recorded

- MultisigSignerDtoOutput
  - `id` string, uuid, required: Entity UUID
  - `createdAt` string, date-time, required: Creation date (ISO 8601)
  - `updatedAt` string, date-time, required: Last update date (ISO 8601)
  - `status` 'requested' | 'validated' | 'active' | 'failed', required: Enrollment status: `requested` (address bound, awaiting a peer validation), `validated` (counts towards the threshold), `active` (in the on-chain signer set, can sign), `failed` (on-chain add exhausted its retries).
  - `user` object, nullable, required: Organization member holding the TSS signing key. Null only when the user reference could not be populated.
    - `id` string, uuid, required: User ID of the signer.
    - `name` string, required: Display name of the signer, as set on their user profile.
    - `email` string, email, required: Email address of the signer.
    - `image` string, nullable, required: Avatar URL of the signer (from their sign-in provider); null without one.
  - `signerType` 'eoa' | 'webauthn', required: Kind of key behind this signer: `eoa` (20-byte secp256k1 address) or `webauthn` (84-byte P-256 passkey signer). Derived from the identity, never stored.
  - `address` string, nullable: ERC-7913 identity of the signing key: a 20-byte EVM address for an `eoa` signer, the 84-byte `verifier ‖ qx ‖ qy` bytes for a `webauthn` one. **Withheld while `requested`**: the SAS a validator must type derives from it, so serving it here would let any client compute the code instead of reading it off the candidate's phone — the whole point of the comparison. Revealed once validated.
  - `validatorNonce` string, nullable: The challenge nonce, once a validator has challenged this enrollment. The candidate app reads it to derive the 4-digit SAS it displays; null before a challenge and after the ceremony settles.
  - `validatedAt` string, date-time, nullable, required: When a peer validated this enrollment. Null while requested.

## Other responses

- `400`: Malformed body, or not exactly one of `address` (EOA) and `publicKey` (passkey) was provided
- `403`: Session-authenticated user required
- `404`: Multisig wallet not found
- `409`: The admin or the key is already enrolled on this wallet, or the network has no WebAuthn verifier for a passkey enrollment
- `422`: The submitted address is the treasury itself, or the public key is not a valid P-256 point

---

[API](https://skmtc.dev/keyban/apis/keyban-rest-apis.md) · [All operations](https://skmtc.dev/keyban/apis/keyban-rest-apis/llms.txt) · [OpenAPI document](https://skmtc.dev/keyban/apis/keyban-rest-apis/revisions/0bb79a1da961?raw)
