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

# Card PINs

> Let cardholders set, change, check, and unblock online PINs on cards.

Cards have no PIN set by default. Until a cardholder sets one, they can enter any PIN at a payment terminal, or skip PIN entry if the terminal allows it.

Once a PIN is set, the cardholder must enter the correct PIN for transactions that use PIN verification. Setting a PIN does not require PIN entry for every purchase; transactions that do not request a PIN continue to work without one.

These APIs support **online PINs only**. Offline PINs, which are checked by a card's chip, are not currently supported.

An online PIN is checked over the payment network when a payment requires it. "Online" describes how the PIN is checked; it does not mean the customer is shopping on a website.

## Use the hosted PIN-entry iframe

<Steps>
  <Step title="Create a session">
    Call `POST /cards/{id}/pin/session` from your backend with the canonical HTTPS `targetOrigin` of your frontend. Include the scheme, host, and optional port, without a path, trailing slash, credentials, query, or fragment.

    Grid returns `iframeUrl`, `sessionToken`, `expiresAt`, and `environment`. The URL already contains the temporary token and selects the appropriate environment.
  </Step>

  <Step title="Load the form">
    Before setting the iframe's `src`, register a `message` listener on your page. Only accept messages whose `origin` equals `new URL(iframeUrl).origin` and whose `source` equals your iframe's `contentWindow`; also require `data.embedType` to equal `PIN_SETTING`.

    Set the iframe's `src` to `iframeUrl` exactly as returned. Wait for `data.messageType` to be `Embed:Rendered` before enabling your submit button. The cardholder enters a four-digit PIN directly into this secure form, so the plaintext PIN never reaches your servers or Grid's.
  </Step>

  <Step title="Submit the PIN">
    When the cardholder clicks your submit button, send `{ messageType: "Embed:SubmitPin", embedType: "PIN_SETTING" }` to the iframe's `contentWindow` using `postMessage`. Set the target origin to `new URL(iframeUrl).origin`; never use `*`. Disable the button while submission is in progress.
  </Step>

  <Step title="Confirm the result">
    Listen for `Embed:PinSubmissionStatusChanged` with the following `data.status` values:

    | Status      | Your app's action                                                                                                 |
    | ----------- | ----------------------------------------------------------------------------------------------------------------- |
    | `STARTED`   | Keep submission disabled while waiting for a result.                                                              |
    | `SUCCEEDED` | Show confirmation, remove the iframe, and refresh the card's PIN status.                                          |
    | `FAILED`    | Show a PIN-entry error and let the cardholder correct the input. Request a new session if the session is expired. |

    Remove your event listener when you remove the iframe. If you stop waiting before a result arrives, treat the outcome as unknown: do not report success or automatically resubmit. An `OK` status alone cannot prove that an already configured PIN was changed.
  </Step>
</Steps>

The session permits one successful submission and expires at `expiresAt`. Request a new session for another PIN change or after expiration. Treat both the URL and token as secrets: never persist, cache, log, or send them to analytics.

## Encrypt a PIN in your own UI

If you collect the PIN in your own UI, you **must encrypt it in the customer’s browser or mobile app before sending it to your server or Grid**. Download the [PIN encryption public key](/keys/pin-encryption-public-key.pem.txt). This PEM-encoded RSA public key is the same for sandbox and production; save it as `pin-encryption-public-key.pem` in your client integration.

1. Collect a four-digit PIN as a string so leading zeros are preserved.
2. Generate a fresh, cryptographically random integer `nonce` for each request.
3. Serialize an object containing `nonce` and `pin` as JSON. Encode the JSON as UTF-8.
4. Encrypt those bytes using the PIN encryption public key, then base64-encode the ciphertext.
5. Send the base64 string as `encryptedPinBlock` in `POST /cards/{id}/pin`.

For example, a PIN of `0123` produces JSON with this shape **before encryption**:

```json theme={null}
{
  "nonce": 582306194725183,
  "pin": "0123"
}
```

Generate your own nonce each time; do not reuse the example. Encrypt the entire JSON payload. Never send this plaintext object to your server or Grid, and keep both plaintext and ciphertext out of logs, analytics, and persistent storage. Grid cannot decrypt the block.

A rejected block returns `400 INVALID_INPUT`. Check the payload, encoding, and public key, then create a new encrypted block with a fresh nonce. A `204` response means the PIN change was accepted; use the status endpoint to check its resulting state.

## Check and recover a PIN

Call `GET /cards/{id}/pin` to read the current `pinStatus`:

| Status    | Next action                                                                       |
| --------- | --------------------------------------------------------------------------------- |
| `NOT_SET` | No PIN is configured. The cardholder can choose a PIN to enable PIN verification. |
| `OK`      | The PIN is configured.                                                            |
| `BLOCKED` | Authenticate the cardholder and offer a PIN change or unblock.                    |

Three consecutive incorrect attempts block an online PIN. Call `POST /cards/{id}/pin/unblock` to keep the same PIN, or use either PIN-entry flow to choose a new one. Unblocking a PIN that is already `OK` is safe; a card with no PIN returns `409 CONFLICT`.

There is no PIN-reveal API. If the cardholder forgets the PIN, let them set a new one. Setting a PIN does not activate a closed or frozen card or change its spending limits.

`Card.pinStatus` is the last known PIN status. It can be absent before the first status check or for a card without PIN management; absence does not mean `NOT_SET`. Use the dedicated status endpoint when current state matters.

## Offline PIN cards are not supported

An offline PIN is checked by a physical card's chip. Physical cards can also use online PINs; the distinction is where the PIN is checked.
