# Card Reveal

[API Documentation](/api/card/create-card-reveal-token)

Column's card reveal SDKs show a card's full number, CVV, expiry and cardholder name to the cardholder, in your web page
or your React Native app. The card data never passes through your code or your servers, so showing it doesn't bring
them into PCI scope.

- **Web:** `@column-na/card-reveal`, from npm or as a hosted script.
- **React Native and Expo:** `@column-na/card-reveal-react-native`.

> **Note**
>
> The card reveal SDKs are proprietary and require an agreement with Column. Contact your Column representative to
> get started.

## How It Works

1. **Your server** creates a single-use reveal token for the card with
   [create card reveal token](/api/card/create-card-reveal-token), using your API key, and sends only the token's
   `id` to the browser or app.
2. **The SDK** reveals the card with that token. It generates a one-time key for each reveal and calls Column's API
   directly. Column encrypts the card details end to end to that key, so nothing between Column and the SDK can read
   them.
   - On the web, each value renders inside its own iframe, served by Column from `https://reveal.column.com`. Your
     page's JavaScript can't read it.
   - In React Native, each value renders inside the SDK's own text component. No callback, ref or return value
     exposes it.

A reveal token is valid for one minute and works once, for one card. Create a new token for every reveal.

## Server: Create a Reveal Token

Create the token right before you show the card, and only after you've checked that the signed-in user may see it.

```bash
curl 'https://api.column.com/issuing/cards/<card_id>/reveal-tokens' \
  -XPOST \
  -u :<YOUR API KEY>
```

```json
{
  "id": "crtk_2Z1qT1aB2cD3eF4gH5iJ6kL7mN8",
  "card_id": "card_2Z1qT0kM6yX9wB3nV5cR8sL4pA7",
  "expires_at": "2026-10-23T18:01:00Z"
}
```

Send the token `id` and the card ID to the browser or app. Don't call `GET /issuing/cards/:card_id/reveal` yourself:
only Column's SDKs can call it, because the response is encrypted to a key the SDK generates. Fetching card details
on your servers would also put them in PCI scope.

## Web

### Install

Install from npm, pinned to an exact version:

```bash
npm install --save-exact @column-na/card-reveal@0.6.0
```

Or load the hosted script, pinned to an exact version with
[Subresource Integrity](https://developer.mozilla.org/en-US/docs/Web/Security/Subresource_Integrity). The hosted
script won't create a client unless its `<script>` tag has an `integrity` attribute. Each version is published once
and never changes.

```html
<script
  src="https://reveal.column.com/0.6.0/card-reveal.js"
  integrity="sha384-Ep7c7/UaCPL1WbDDj1eKtsEttBjBCSwCttMpCPyk0A+XyMn9Gk9Vzl8A0xikWQuW"
  crossorigin="anonymous"
></script>
```

| Version ::min | Script                                           | Integrity                                                                 |
| ------------- | ------------------------------------------------ | ------------------------------------------------------------------------- |
| `0.6.0`       | `https://reveal.column.com/0.6.0/card-reveal.js` | `sha384-Ep7c7/UaCPL1WbDDj1eKtsEttBjBCSwCttMpCPyk0A+XyMn9Gk9Vzl8A0xikWQuW` |

### Reveal a Card

```ts
import { createCardRevealClient, CardRevealError } from '@column-na/card-reveal';
// Hosted script: const { createCardRevealClient, CardRevealError } = window.ColumnCardReveal;

// Create one client and reuse it.
const client = createCardRevealClient({ environment: 'live' }); // or 'sandbox'

const { ready, unmount } = client.reveal({
  cardId: 'card_2Z1qT0kM6yX9wB3nV5cR8sL4pA7',
  revealToken: 'crtk_2Z1qT1aB2cD3eF4gH5iJ6kL7mN8',
  fields: {
    pan: '#card-number',
    cvv: '#cvv',
    cardholder_name: '#name',
    expiration_month: '#exp-month',
    expiration_year: '#exp-year',
  },
  style: { color: '#fff', 'font-family': 'ui-monospace, monospace', 'font-size': '15px' },
  fieldStyles: { pan: { 'font-size': '19px' } },
  format: { pan: 'spaced', expirationYear: 'YY' },
});

try {
  await ready;
} catch (error) {
  if (CardRevealError.is(error)) showMessage(error.code);
}

// When the cardholder hides the card, or the page or component goes away:
unmount();
```

Each field renders as an iframe that fills its container, so give each container a width and height. In React, call
`reveal` in an effect and return `unmount` as its cleanup.

### Content Security Policy

If your page has a Content Security Policy, allow Column's reveal frame:

```text
script-src  'self' https://reveal.column.com;
frame-src   https://reveal.column.com;
```

Leave `https://reveal.column.com` out of `script-src` if you install from npm. Your page doesn't need any
`connect-src` entry or `'unsafe-inline'` for the SDK, in sandbox or live.

## React Native

The React Native SDK is pure JavaScript with no native module. It works in Expo Go, Expo development builds and bare
React Native, and needs React 19.1 and React Native 0.81 or later.

### Install

```bash
npx expo install @column-na/card-reveal-react-native@0.6.0 react-native-get-random-values
```

Check that `package.json` lists `"@column-na/card-reveal-react-native": "0.6.0"` with no `^` or `~`, or install with
`npm install --save-exact @column-na/card-reveal-react-native@0.6.0 react-native-get-random-values`.

Each reveal generates a one-time key, which needs a secure random source. Import the polyfill once, at the top of your
app's entry file, before anything else:

```ts
import 'react-native-get-random-values';
```

Without it, `reveal()` throws `invalid_options`.

### Reveal a Card

```tsx
import { useEffect, useMemo, useRef } from 'react';
import { View } from 'react-native';
import {
  CardRevealError,
  CardRevealText,
  createCardRevealClient,
  type CardRevealTextHandle,
} from '@column-na/card-reveal-react-native';

export function CardDetails({ cardId, revealToken }: { cardId: string; revealToken: string }) {
  const client = useMemo(() => createCardRevealClient({ environment: 'live' }), []); // or 'sandbox'
  const pan = useRef<CardRevealTextHandle>(null);
  const cvv = useRef<CardRevealTextHandle>(null);

  useEffect(() => {
    const { ready, unmount } = client.reveal({
      cardId,
      revealToken, // single use: create a new one for every reveal
      fields: { pan, cvv },
      style: { color: '#fff', fontFamily: 'Menlo' },
      format: { pan: 'spaced' },
    });
    ready.catch((error) => {
      if (CardRevealError.is(error)) console.warn('Card reveal failed:', error.code);
    });
    return unmount;
  }, [client, cardId, revealToken]);

  return (
    <View>
      <CardRevealText ref={pan} placeholder="•••• •••• •••• ••••" />
      <CardRevealText ref={cvv} placeholder="•••" />
    </View>
  );
}
```

`CardRevealText` shows its `placeholder` before the reveal and after `unmount`. Set `selectable` to let the cardholder
select and copy the value. React Native Web isn't supported for live reveals; use the web SDK instead.

## Reference

### Reveal Options

| Option ::min            | Description                                                                                                                                                                                                             |
| ----------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `cardId`                | The card's ID (`card_...`).                                                                                                                                                                                             |
| `revealToken`           | The reveal token ID (`crtk_...`) from your server.                                                                                                                                                                      |
| `fields`                | Maps any of `pan`, `cvv`, `cardholder_name`, `expiration_month` and `expiration_year` to where it renders: a CSS selector on the web, a `CardRevealText` ref in React Native. Include only the fields you want to show. |
| `style`                 | Styles for every field. On the web, CSS properties (kebab-case or camelCase), plus `'&:hover'`, `@media` and `@font-face` blocks. In React Native, text styles.                                                         |
| `fieldStyles`           | Per-field styles, applied on top of `style`.                                                                                                                                                                            |
| `format.pan`            | `'spaced'` (default, `4111 1111 1111 1111`) or `'plain'`.                                                                                                                                                               |
| `format.expirationYear` | `'YYYY'` (default) or `'YY'`.                                                                                                                                                                                           |
| `timeoutMs`             | How long `ready` waits before it rejects with `timeout`. Default `15000`, maximum `120000`.                                                                                                                             |

### `ready` and `unmount`

- `ready` resolves once every requested field shows its value.
- If any field fails or the timeout passes, `ready` rejects and every field is removed. A reveal shows all of its
  fields or none of them.
- `unmount()` removes every field. If `ready` is still pending, it rejects with `unmounted`.
- Invalid options throw `invalid_options` synchronously from `reveal()`.

### Errors

Every error is a `CardRevealError` with a stable `code`. Check with `CardRevealError.is(error)`. `field` and the HTTP
`status` are set when known. Messages never contain the token or card data, and the SDKs never log either.

| Code ::min            | Meaning                                                                                                                       |
| --------------------- | ----------------------------------------------------------------------------------------------------------------------------- |
| `invalid_options`     | Bad options, or an unsupported environment. Thrown synchronously.                                                             |
| `integrity_required`  | Web hosted script only: its `<script>` tag has no `integrity` attribute.                                                      |
| `script_load_failed`  | Web only: Column's reveal frame couldn't load. The next `reveal()` retries.                                                   |
| `container_not_found` | A field's container doesn't exist: no element matches the selector, or the ref isn't attached.                                |
| `reveal_failed`       | The reveal request failed. A `401` status means the token is missing, invalid, already used, expired or for a different card. |
| `timeout`             | `timeoutMs` passed before every field showed its value.                                                                       |
| `unmounted`           | `unmount()` was called before the reveal finished.                                                                            |

## Sandbox

Sandbox works the same way, with a client created with `environment: 'sandbox'` and a reveal token for a sandbox
card, so you can build and test the whole flow before going live. See
[Card Sandbox Simulation](/card-issuing/card-sandbox-simulation) to create a sandbox card.

## Versions

The web and React Native SDKs follow semantic versioning and release together at the same version. Pin an exact
version. Each request tells Column which SDK and version made it, and Column may require a minimum version to keep
revealing cards. Versions before 0.5.0 no longer reveal cards; upgrade to the latest version.
