# Reusing a stored payment method with payment tokens

Capture the reusable payment token returned when a card is saved, then charge the stored card again without handling card data, declaring whether the customer is present for each charge, including a payment taken over the phone.

**Category:** Integration

**Last reviewed:** 4 November 2026

# Reusing a stored payment method with payment tokens

When a customer saves their card during a payment (for example, a Hosted Payment Page or Virtual Terminal sale with save-card enabled), WinkPG vaults the card and returns a reusable **payment token**. You can charge that token later without ever collecting or storing the card number yourself, which keeps the reorder and card-on-file flows out of your PCI scope.

The token you use for this is the **public reference**: a single opaque handle that's both what you receive and what you charge with.

## Where the token appears

The public reference is surfaced in two places whenever a transaction tokenizes a card:

- **The synchronous transaction response** at `responseData.tokenResult.publicReference`.
- **The transaction-completed webhook** at `data.responseData.tokenResult.publicReference` (see the Webhook Integration guide for the envelope and signature scheme).

Both carry the same value. It's present only when the transaction actually saved a card; on a non-tokenizing transaction the `tokenResult` object is omitted.

```json
{
  "responseData": {
    "resultCode": 1,
    "resultMessage": "Approved",
    "tokenResult": {
      "publicReference": "pt_9fKq2ZmB7tLxW3aH5nR8cV1s"
    }
  }
}
```

## What the token is

- **Opaque and self-describing.** It always begins with the `pt_` prefix followed by a random string safe for use in a web address. Treat the whole value as an opaque handle: store it and send it back verbatim. Don't parse, split, or infer anything from its contents.
- **Not a card number and not an internal id.** By design, the public reference isn't shaped like a card number, and it's not the gateway's internal vault identifier. It's the only token identifier WinkPG emits to you; the internal identifier never leaves the gateway.
- **Stable.** The same stored payment method keeps the same public reference, so you can store it against your customer record and reuse it across orders.

## Charge with the token

To charge a stored payment method, send the public reference in the payment token field of a sale or authorization request instead of raw card data:

```json
{
  "tokenData": {
    "token": "pt_9fKq2ZmB7tLxW3aH5nR8cV1s"
  },
  "invoiceData": {
    "amounts": { "base": 49.00 }
  }
}
```

WinkPG resolves the token to the stored card, runs the charge, and returns the usual transaction response.

**Send the amount as `base`, not `total`.** `total` is calculated by WinkPG as `base + tip + tax + shipping + convenience`. It's returned on the response; it's not a way to set the amount charged, and a `total` that disagrees with the components you sent doesn't change what's charged. If you do send `total`, it's treated as an integrity check on those components. Once checksum enforcement is switched on for your environment, a disagreement is rejected with an error naming both values; until then it's recorded for review and the charge proceeds for the component sum. Either way, send a `total` only if you want that check. Always read the `total` on the response as the authoritative amount charged: fees the gateway adds after accepting your request, such as a surcharge or a convenience fee it computes, are outside the check and appear only there.

## Say who initiated the charge

Card network rules require every charge against a stored payment method to say who initiated it, and WinkPG reports that to the processor from one field on the request: `initiationType`. The rule is about the customer's presence at the moment of the charge. It isn't about which system sends the request (your platform sends every request), and it isn't about what's being sold.

- The customer is present and agreeing to this charge right now, at a counter, on a kiosk, on a checkout page in front of them, or on a phone call: a **cardholder-initiated (CIT)** charge.
- The customer isn't present and you're charging on their behalf, for a subscription, an unpaid balance, a no-show fee, or an order you fulfill later: a **merchant-initiated (MIT)** charge.

The same stored payment method can be charged both ways. Decide per charge, not per integration.

### When the customer is present (CIT)

Send `initiationType` as `CardholderInitiated`, or leave it out; an omitted value is treated as cardholder-initiated. Don't send a `mitReason`.

```json
{
  "initiationType": "CardholderInitiated",
  "tokenData": {
    "token": "pt_9fKq2ZmB7tLxW3aH5nR8cV1s"
  },
  "invoiceData": {
    "customerId": "3f2a6c1e-8d4b-4f0a-9b7e-2c5d1a8e6f30",
    "amounts": { "base": 49.00 }
  }
}
```

WinkPG recognizes a cardholder-initiated charge that pays with a stored token as a later use of a stored payment method and reports it to the processor as one, referencing the network transaction that stored the card. No consent is consulted, so this works for any active stored payment method, including one saved without consent. `invoiceData.customerId` isn't enforced on a cardholder-initiated charge, but send it so the token resolves scoped to the customer who owns it.

### When the customer isn't present (MIT)

Send `initiationType` as `MerchantInitiated`, a `mitReason`, and the owning customer in `invoiceData.customerId`. All three are required. A merchant-initiated token charge without `customerId` is rejected before any consent is checked.

```json
{
  "initiationType": "MerchantInitiated",
  "mitReason": "UnscheduledCOF",
  "tokenData": {
    "token": "pt_9fKq2ZmB7tLxW3aH5nR8cV1s"
  },
  "invoiceData": {
    "customerId": "3f2a6c1e-8d4b-4f0a-9b7e-2c5d1a8e6f30",
    "amounts": { "base": 49.00 }
  }
}
```

A merchant-initiated charge also needs a captured **stored-credential consent** that permits the reason: `Recurring` for a fixed schedule, `Installment` for a known total split into payments, and `UnscheduledCOF` for anything with no fixed schedule (it also covers `DelayedCharge`, `NoShow`, and `IncrementalAuth`). WinkPG resolves the active consent from the token and customer for you, so don't look up or send a consent id. A merchant-initiated charge with no consent that permits its reason is declined with `stored_credential_consent_required`.

Consent is captured when the card is saved, on a hosted payment page with save-card enabled or in the Virtual Terminal; the direct API can't record it. Ask for every usage you expect to need at that moment, because a later merchant-initiated charge can't add one. [Setting up a hosted payment page](https://devportal.signapay-sbx.winkpg.io/docs/guides/hosted-payment-page-setup.md) covers the save-card purposes and the session-level consent settings.

Sending `MerchantInitiated` for a purchase the customer is making in front of you isn't rejected, but it misreports the charge to the networks, which price and dispute the two kinds differently. Sending a cardholder-initiated charge for one the customer never saw is the same mistake in the other direction.

### Charge with a companion `cardData`

A token charge doesn't need a `cardData` object at all: the token is the tender, and WinkPG resolves the card number and expiration from the vault. You may still send one to carry **address-verification fields**, which is the only reason to include it:

```json
{
  "tokenData": {
    "token": "pt_9fKq2ZmB7tLxW3aH5nR8cV1s"
  },
  "cardData": {
    "billingAddress": {
      "address1": "1 Market Street",
      "city": "San Francisco",
      "state": "CA",
      "zip": "94105",
      "country": "US"
    }
  },
  "invoiceData": {
    "amounts": { "base": 49.00 }
  }
}
```

Put the postal code in the field that matches the address country. For an address in the United States, send the ZIP code in `zip`. For any other country, send the postal code in `postalCode`. Address verification reads `zip` when `country` names the United States (`US`, `USA`, `United States`, or `United States of America`) or is left out, and reads `postalCode` for every other country, whichever processor runs the charge. You don't need to send the same value in both fields.

Three things to know about that companion object:

- **Its `billingAddress` replaces the stored one as a whole.** When a token charge carries a `cardData.billingAddress` with any field set, WinkPG sends that address for address verification exactly as you sent it, and the address stored with the token isn't used for any of its fields. A `billingAddress` with only a postal code sends only a postal code. Leave `billingAddress` out to verify against the stored address, or send the whole address. The stored billing state is the one exception: when your `billingAddress` has no `state`, a surcharge decision on the charge uses the state stored with the token, without adding it to the address that's verified.
- **Send AVS fields only.** A `cardData` on a token charge should carry the billing address, and optionally the cardholder name, email, or phone. Don't put a card number, track data, or a reader payload in it: the token already identifies the card, and a request that supplies both a token and raw card data is rejected as ambiguous about which one to charge.
- **You don't need to set `isStoredPayment`.** WinkPG stamps that flag itself while resolving the token. It's an output of token resolution, not something the caller declares, so leave it out and let the gateway set it.

Don't declare a card-present `entryMode` (a magnetic-stripe, chip, or contactless value) on a companion object. Those values assert that a physical card was read on a device, so the request is then held to carrying the reader payload that read produced. Omit `entryMode` on a token charge.

## Take a phone payment on a stored card

When a customer calls and asks you to charge a card they saved earlier, they're present on the call and agreeing to the charge right now, so it's a **cardholder-initiated** charge. Send `initiationType` as `CardholderInitiated` (or leave it out), with no `mitReason`. No stored-credential consent is consulted.

```json
{
  "initiationType": "CardholderInitiated",
  "cardholderPresence": "MOTO",
  "tokenData": {
    "token": "pt_9fKq2ZmB7tLxW3aH5nR8cV1s"
  },
  "cardData": {
    "billingAddress": {
      "address1": "1 Market Street",
      "city": "San Francisco",
      "state": "CA",
      "zip": "94105",
      "country": "US"
    }
  },
  "invoiceData": {
    "customerId": "3f2a6c1e-8d4b-4f0a-9b7e-2c5d1a8e6f30",
    "amounts": { "base": 49.00 }
  }
}
```

### What `cardholderPresence` changes

`cardholderPresence: "MOTO"` records that the customer was on the phone or sent the order by mail. WinkPG returns it in `cardholderPresence` on the transaction and uses it as the transaction's effective presence.

It doesn't change the channel reported to the card networks. The **industry configured on the processor profile** that runs the charge decides that: a profile set up for mail and telephone orders reports the charge as one, and a profile set up for e-commerce reports it as e-commerce, whatever presence the request declares. To report phone payments to the networks as mail or telephone orders, route them through a processor profile configured for that industry. Your administrator can tell you which profile charges your phone payments.

### Check the presence that was used

The transaction response carries two presence fields:

- `cardholderPresence` echoes the value your request declared, and is `null` when you declared none.
- `effectiveCardholderPresence` is the presence WinkPG used for the charge. It's your declared value when you sent one. Otherwise, it's inferred from `initiationType`, the transaction source and the card's entry mode: a merchant-initiated charge is `Recurring`, a Hosted Payment Page charge is `ECommerce`, a manually keyed card is `MOTO`, a card read on a device is `CardPresent`, and anything else is `Unknown`.

A token charge takes its entry mode from the card it charges. A card saved from the card form on a Hosted Payment Page, or keyed into the Virtual Terminal, is stored with a manually keyed entry mode, so a later API charge on that token with no declared presence and no companion `cardData` reports `MOTO`. A token charge that carries a companion `cardData` takes its entry mode from that object instead, and because you leave `entryMode` out of it, the inferred presence is `Unknown`. A card saved from a digital wallet also gives `Unknown`. To record a phone payment as one, declare `cardholderPresence: "MOTO"` rather than relying on the inference.

For the processors that return them, `responseData` also reports the channel indicators that were sent: `posDataCode` and `eci`, or the `POS Condition Code` and, on an American Express charge, the `MOTO Indicator` entries in `responseData.additionalProcessorData`.

### Send the billing ZIP code for address verification

A phone payment has no card in hand, so address verification is the main check that the caller knows the card. Send the customer's billing address in `cardData.billingAddress`, with the ZIP code in `zip` for an address in the United States or the postal code in `postalCode` for any other country, as the companion `cardData` section above describes. Remember that a companion address replaces the stored one as a whole, so send the full address.

## Treat the token as a credential

Making the public reference chargeable means anyone who holds it can attempt a charge, subject to two protections that always apply: the token is scoped to your merchant account, and merchant-initiated reuse still requires stored-credential consent. The token's shape isn't a secret in itself, so:

- Keep the token server-side. Don't embed it in browser code, query strings, or client-visible URLs.
- Store it with the same care you give any reusable credential, and only alongside the customer it belongs to.
- Log it sparingly. It's not card data, but it's a live charging handle.

## What not to rely on

Charge only with the `publicReference` value. Don't attempt to charge using identifiers scraped from a transaction's history or from internal fields: those aren't resolvable for charging and aren't a supported integration surface. The public reference in the response and webhook is the one supported reusable handle.

## See also

- The **Inbound vault routes and the CVV alias charge** guide covers a card your own card form captured through an inbound vault route: the `pt_` reference your server receives is a public reference like the one on this page, and that guide shows charging it with its short-lived CVV alias. It appears under Guides once vault routes are turned on for your account.

## See also

- [All documentation](https://devportal.signapay-sbx.winkpg.io/llms.txt): the machine-readable index of every public page on this site.
