Source: https://docs.cumuluslabs.io/sdk-private
Content version: 58b939ab

# Private keypad verification and tokenization

Some phone workflows need a caller to enter digits without speaking them to the conversational model. Cumulus provides two steps for this: **verification** compares keypad input with authorized expected values; **tokenization** sends private input to your vault and returns a token the rest of the flow can use.

For example, a caller can verify a PIN, consent to storing a reference, enter the reference on the keypad and continue after your vault returns a token. A failed match or declined consent follows a separate exit.


These nodes require telephone keypad input. Use synthetic values and a sandbox vault while developing. Tokenization stores a reference; any later payment or other business operation belongs to your integration.

## 1. Prepare the subject and expected values

Verification needs a trusted subject identifier and the expected value for each enabled method. Obtain them from your authorized context resolver or account lookup. A telephone-number match can identify a candidate account; the verification step still needs to check the entered value.

Mark every expected-value alias in the native `privateAliases` policy. An ordinary graph variable is not private merely because you give it a name such as `expected_pin`. Keep expected values out of conversational prompts and ordinary tool output.

If the caller must be looked up first, declare that lookup tool in the agent and map all required verification variables. Use `responseVariablePolicy: 'replace_available'` so an absent value clears the previous lookup's value. See [response mapping](/docs/sdk-definitions#connect-a-tool).

## 2. Define verification

Import `defineVerification` from `@cumulus/sdk/agent`. This example enables one four-digit PIN check:

```typescript
const verify = defineVerification({
  key: 'verify', matched: 'capture', declined: 'declined',
  policy: {
    subjectVariable: 'subject', timeoutMs: 60000,
    required: { kind: 'fixed', count: 1 },
    fields: [{
      id: 'pin', expectedVariable: 'expected_pin', enabledVariable: 'pin_allowed',
      format: { kind: 'digits', length: 4 },
      prompt: {
        en: 'Enter your four-digit PIN, then press pound. Press star to cancel.',
      },
    }],
  },
});
```

`subject`, `expected_pin` and `pin_allowed` refer to the authorized values prepared above. `matched` and `declined` are destination node keys in the same graph. The helper creates the required native outcome edges; the platform decides the result from the private verification receipt.

## 3. Define the capture step

Declare the vault as a tool with `purpose: 'tokenization'`, HTTP `POST` and `idempotency: { mode: 'effect_key' }`. Its input schema describes the private fields and its output schema describes the returned token. Bind its endpoint and credentials through [environment bindings](/docs/sdk-bindings).

Given that tool as `vault`, define the capture:

```typescript
const capture = defineTokenization({
  key: 'capture', tool: vault, tokenized: 'done', declined: 'declined',
  policy: {
    subjectVariable: 'subject', timeoutMs: 60000,
    consentPrompt: {
      en: 'To store your reference, press 1 followed by pound. To decline, press 0 followed by pound.',
    },
    fields: [{
      id: 'reference', parameter: 'reference', minDigits: 8, maxDigits: 8,
      encoding: 'string',
      prompt: {
        en: 'Enter your eight-digit reference, then press pound. Press star to cancel.',
      },
    }],
    arguments: [], tokenIdPath: 'id', tokenVariable: 'vault_token',
    last4Variable: 'reference_last4', last4Field: 'reference',
  },
});
```

The vault receives `reference`; later steps receive `vault_token` and the permitted last-four suffix. `tokenIdPath: 'id'` must match the response field containing the UUID token. Use pound (`#`) to submit and star (`*`) to cancel in the caller instructions.

The package includes a complete synthetic definition at `node_modules/@cumulus/sdk/examples/private-capture.mjs`, with both nodes and the vault contract. Importing it only builds the definition. Add the application's project/environment exports and follow the normal [preview and publication workflow](/docs/sdk-cli).

## 4. Implement the vault endpoint

Use `implementTool(vault, handler)` with `createTokenizationHandler(implementation, options)` from `@cumulus/sdk/server`. Configure the same authenticated integration as the binding and pass `tokenIdPath: 'id'` in the handler options. Mount it with `nodeHandler` before JSON middleware, as in [customer HTTP adapters](/docs/sdk-server).

Your handler calls the vault and returns an object such as:

```json
{ "id": "9e89b3a8-a97a-41d1-9692-55832c5194ba" }
```

The dedicated adapter returns only the configured token field. It bounds the request to at most 16 KiB and rejects invalid token responses. The ordinary tool handler refuses tokenization-purpose tools.

Persist the operation's intent and result under `context.integration` and `context.effectId`. If the vault response is lost, look up that operation before requesting another capture. Throw `TokenizationDeclined` only when the vault definitely refused and created no token; this produces HTTP 422. Other post-invocation errors produce an uncertain HTTP 500 outcome that needs reconciliation.

## 5. Test the complete telephone flow

First test the endpoint with synthetic inputs, then publish a candidate for a dedicated number. Exercise a correct PIN, mismatch, declined consent, cancellation, timeout and an uncertain vault response. Check the spoken instructions, outcome path and stored token independently.

The supported capture policy accepts bounded digit strings or integers, English/Spanish prompts and UUID token responses. Keep raw digits and provider error payloads out of logs, tracing, screenshots and recordings. The adapter clears its owned request buffer; your vault client and hosting environment also need appropriate handling for the values they receive.
