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

# Publishable-key deposits

> Create a Deposit session in the browser with a destination-binding artifact.

<Note>
  This is the browser-side `pk_` surface. A publishable key is safe to ship in
  front-end code; a secret `sk_` key is not. The server still decides whether the
  key's origin and partner are enabled.
</Note>

`createPublishableDeposit` creates a Deposit session without putting a
destination in the browser request. Supply exactly one binding artifact:
`candidateId`, `grant`, or `challenge`.

## Candidate selection

`candidateId` is the no-secret, no-signature path. The merchant registers the
destination candidates server-side; the browser selects one by its opaque,
partner-scoped id. It needs no merchant backend at runtime:

```typescript theme={"system"}
import { createPublishableDeposit } from "@hypermid/sdk/publishable-deposit";

const session = await createPublishableDeposit(
  {
    orderId: "order-8842",
    candidateId: selectedCandidateId,
  },
  { publishableKey: "pk_live_…" },
);

// A session is not proof of payment.
showCheckout(session.id);
```

The candidate request accepts only `orderId` and `candidateId`. Metadata,
redirects and expiry come from the stored intent and cannot be supplied on
this path.

## Payer proof

### SIWE challenge

The challenge path asks the payer to sign a server-issued message. It is
EVM-only. It is useful when the payer is proving control of a destination
address, but it is not a free-form destination field:

```typescript theme={"system"}
import {
  createPublishableDeposit,
  issueDepositChallenge,
} from "@hypermid/sdk/publishable-deposit";

const opts = { publishableKey: "pk_live_…" };
const challenge = await issueDepositChallenge(
  { chainId: 8453, address: payerAddress },
  opts,
);
const signature = await wallet.signMessage({ message: challenge.message });

const session = await createPublishableDeposit(
  {
    orderId: "order-8842",
    challenge: { nonce: challenge.nonce, signature },
    metadata: { order: "8842" },
    expiresIn: 1800,
  },
  opts,
);
```

The challenge expires at `expiresAt`. The proof contains the issued `nonce`
and the payer's `signature`.

### Server-minted grant

A grant is a single-use JWS minted by the merchant's server. It binds the
destination without exposing a secret key, but it **does require a merchant
backend** to issue the grant:

```typescript theme={"system"}
const session = await createPublishableDeposit(
  {
    orderId: "order-8842",
    grant: serverMintedGrant,
    successUrl: "https://shop.example/thanks",
    cancelUrl: "https://shop.example/cart",
  },
  { publishableKey: "pk_live_…" },
);
```

Grant and challenge requests may also include `metadata`, `successUrl`,
`cancelUrl`, and `expiresIn` (session lifetime in seconds).

## Functions and types

| Function                              | Wire request                                      | Returns            |
| ------------------------------------- | ------------------------------------------------- | ------------------ |
| `issueDepositChallenge(req, opts)`    | `POST /v1/payments/deposit/publishable/challenge` | `DepositChallenge` |
| `createPublishableDeposit(req, opts)` | `POST /v1/payments/deposit/publishable`           | `PaymentSession`   |

```typescript theme={"system"}
function issueDepositChallenge(
  req: IssueDepositChallengeRequest,
  opts: PublishableDepositOptions,
): Promise<DepositChallenge>;
function createPublishableDeposit(
  req: CreatePublishableDepositRequest,
  opts: PublishableDepositOptions,
): Promise<PaymentSession>;
```

```typescript theme={"system"}
interface PublishableDepositOptions {
  publishableKey: string;
  apiBase?: string;       // defaults to https://server.hypermid.io
  signal?: AbortSignal;
}

interface IssueDepositChallengeRequest {
  chainId: number;
  address: string;
}

interface DepositChallenge {
  nonce: string;
  message: string;
  expiresAt: string;
}
```

The create request is this union:

```typescript theme={"system"}
interface CandidateDepositRequest {
  orderId: string;
  candidateId: string;
}

interface GrantDepositRequest {
  orderId: string;
  grant: string;
  metadata?: Record<string, unknown>;
  successUrl?: string;
  cancelUrl?: string;
  expiresIn?: number;
}

interface ChallengeDepositRequest {
  orderId: string;
  challenge: { nonce: string; signature: string };
  metadata?: Record<string, unknown>;
  successUrl?: string;
  cancelUrl?: string;
  expiresIn?: number;
}

type CreatePublishableDepositRequest =
  | CandidateDepositRequest
  | GrantDepositRequest
  | ChallengeDepositRequest;
```

## Branchable errors

The SDK maps the first integration failures to stable error codes. Branch on
`HypermidError.code`, not on the message. `status`, `meta.requestId`, and the
server's original code in `details.serverCode` are preserved.

| Code                              | HTTP status | Meaning                                                                               | What to do                                                               |
| --------------------------------- | ----------: | ------------------------------------------------------------------------------------- | ------------------------------------------------------------------------ |
| `ORIGIN_NOT_CONFIGURED`           |         403 | A legacy key has no allowed origins. New publishable keys require at least one.       | Add this site's exact origin to the key's allowed-origins configuration. |
| `ORIGIN_NOT_ALLOWED`              |         403 | The calling page's origin is not on the key's allowlist.                              | Add the exact page origin to the key configuration.                      |
| `PUBLISHABLE_DEPOSIT_NOT_ENABLED` |         403 | Publishable-key deposits are not enabled for the partner. This is not a 404.          | Ask for the partner opt-in to be enabled.                                |
| `AMBIGUOUS_DEPOSIT_ASSET`         |         400 | The proven chain does not have exactly one configured deposit asset for SIWE binding. | Configure one asset for the chain, or use `candidateId`.                 |
| `SIWE_EVM_ONLY`                   |         400 | SIWE proof was attempted for a non-EVM chain.                                         | Use `candidateId` for a non-EVM destination.                             |
| `SECRET_KEY_USED`                 |         401 | A secret key was sent where a publishable key is required.                            | Replace it with a `pk_` key; never put `sk_` in browser code.            |

The SDK also rejects a key beginning with `sk_` locally, before making a
request, with a regular `Error`. `SECRET_KEY_USED` is the `HypermidError` code
for the corresponding 401 response when the server sees the invalid key.

```typescript theme={"system"}
import {
  PublishableDepositErrorCode,
  createPublishableDeposit,
} from "@hypermid/sdk/publishable-deposit";
import { HypermidError } from "@hypermid/sdk/errors";

try {
  await createPublishableDeposit(request, { publishableKey: "pk_live_…" });
} catch (error) {
  if (error instanceof HypermidError &&
      error.code === PublishableDepositErrorCode.OriginNotConfigured) {
    showSetupMessage("Add this site's origin to the publishable-key configuration.");
  }
}
```

The SDK also rejects a missing `orderId` or a request with anything other than
one binding artifact before sending a request. A returned `PaymentSession`
means a session was created; determine payment status separately.
