Skip to content

Card Tokenization (Consent Link)

Card Tokenization lets you store a customer's card for future use — without ever handling, seeing, or encrypting their card details yourself.

Instead of collecting card data directly, you generate a consent link and send it to your customer. They open it, see clearly which business is asking to store their card, agree, and enter their card details on a page PayOnUs hosts and secures. Once they're done, you're notified and can retrieve the resulting card token.

There is no API to tokenize a card directly. Every card token is created through this consent-link flow — this is intentional: it guarantees a customer has explicitly agreed to let your business store their card before any tokenization happens.

How It Works

  1. You create a link — call Create Card Tokenization Link with your businessId, the customer's details, and your own merchantReference for the transaction/order this token relates to.
  2. You send the link to your customer — the API returns a url. Send it however you'd reach your customer (SMS, email, chat, in-app message). How you deliver it is entirely up to you.
  3. Your customer opens the link — they land on a PayOnUs-hosted page showing your business name and a plain-language consent notice explaining that they're authorizing your business to store their card for future charges.
  4. Your customer reviews and submits — they check the consent box, enter their card details, and submit. Card data is encrypted in the customer's browser and never touches your servers.
  5. PayOnUs tokenizes the card — once consent and card details are validated, the card is tokenized with the payment gateway. Only a masked PAN and a gateway token are stored — the raw card number is never persisted. Your customer sees a success or failure page.
  6. You're notified — PayOnUs sends a webhook to your configured webhook URL once the link completes (see Webhook Notification).
  7. You retrieve the token — look up the link's status by its id or by your own merchantReference, search across your links with more filters, or search all of your business's card tokens directly.
  8. You charge the card whenever you need to — call Charging a Tokenized Card with the token's id and an amount. No further action from the customer is needed.
  9. You revoke the card whenever it's no longer needed — call Delete a Card Token to permanently remove it, both with us and at the payment gateway.
Merchant                         Customer's browser                    PayOnUs
────────                         ──────────────────                    ───────
POST /card-tokenization-links ──────────────────────────────────────►  creates link (PENDING)
        │
        │  { url, merchantReference, expiresAt }
        ▼
send `url` to customer
 (SMS / email / chat)
        │
        │                        opens `url`
        │                        ─────────────────────────────────►  hosted consent + card page
        │                        reviews consent, enters card
        │                        submits ─────────────────────────►  validates, tokenizes, stores
        │                                                              masked PAN + gateway token
        │                        sees success/failure page ◄────────
        │
        │  ◄─────────────────────────────────────────────────────── webhook: link completed
        │
GET /card-tokenization-links/{id}   or   GET /card-tokens (search)

Request

  • Path: /api/v1/card-tokenization-links
  • Method: POST

Request Body Table

Field Type Required Description
businessId string Yes Your Business ID
customer object Yes Customer information
customer.name string Yes Customer's full name
customer.email string Yes Customer's email address
customer.phone string No Customer's phone number, international format
customer.externalId string No Your own identifier for this customer, if you have one
merchantReference string Yes Your own reference for this tokenization request (e.g. an order or subscription ID). Max 100 characters.
expiryMinutes number No How long the link stays valid, in minutes. Defaults to 30, capped at 1440 (24 hours) if a larger value is supplied.

Sample Request

{
  "businessId": "{business_id}",
  "customer": {
    "name": "Jane Doe",
    "email": "jane.doe@example.com",
    "phone": "+2348012345678"
  },
  "merchantReference": "order-58213",
  "expiryMinutes": 30
}

Sample Response

{
  "status": 200,
  "message": "Success",
  "data": {
    "id": "xxx-xxx-xxx",
    "url": "https://core-sandbox.payonus.com/card-consent/aB3dEfGhIj...",
    "expiresAt": "2026-09-12 15:30:00",
    "status": "PENDING",
    "merchantReference": "order-58213",
    "businessName": "Acme Technologies Ltd",
    "merchantName": "Acme Technologies Ltd",
    "businessId": "xxx-xxx-xxx"
  }
}

Send your customer the url value — that's the entire integration on your side for the consent-collection step. You do not need to build a card-capture form, and you never receive or handle card data.

Possible Errors

Status Meaning
403 Card tokenization is not enabled for your business. Contact support.
409 You already have a link with this exact merchantReference for this business - regardless of its status. Use a different reference for a new request.

You can check on a link two ways — by the id returned when you created it, or by your own merchantReference.

By ID

  • Path: /api/v1/card-tokenization-links/{id}
  • Method: GET
  • Query Parameter: businessId (required)

By Merchant Reference

  • Path: /api/v1/card-tokenization-links/by-reference/{merchantReference}
  • Method: GET
  • Query Parameter: businessId (required)

Since a merchantReference can only ever be used once per business (see below), this returns the single link created with that reference.

Sample Response

Same shape as the create response above, reflecting the link's current status.

Status Meaning
PENDING Link is active and awaiting the customer - this includes a customer who has already tried and failed once (e.g. an invalid card, or a momentary gateway issue) and can try again
COMPLETED Customer consented and the card was tokenized successfully
EXPIRED Customer did not complete the link before expiresAt
CANCELLED Link was cancelled after too many submission attempts

A failed submission attempt (invalid card details, a declined card, a temporary gateway issue, etc.) does not end the link - it's deliberately left PENDING so your customer can simply try again with the same link, rather than you having to generate and re-send a new one. A link only stops being usable when it expires, is cancelled after too many attempts, or completes successfully.

A link is single-use: it can't be reused after it's COMPLETED, EXPIRED, or CANCELLED. If you need a link for the same order after one of those, create a new one with a different merchantReference - a reference is permanently tied to the link it was first used for and can never be reused for that business, regardless of that link's status.

Look up your consent links with more flexible filtering than the two lookups above — useful for building a dashboard view, reconciling a batch of orders, or checking on links across a date range.

  • Path: /api/v1/card-tokenization-links
  • Method: GET

Query Parameters (all optional except where noted)

Parameter Type Description
merchantId string Your Merchant ID. Required for merchant API-key access.
businessIds array One or more Business IDs to restrict the search to (defaults to all your businesses if omitted)
id string A specific link's ID
merchantReference string Your own reference from link creation
status string One of the link statuses above
hasCardToken boolean true to only return links that have generated a card token; false for links that haven't (yet)
createdFrom date Only links created on/after this date (yyyy-MM-dd)
createdTo date Only links created on/before this date (yyyy-MM-dd)
pageNo number Page number, starting at 1 (default: 1)
pageSize number Results per page (default: 30)

Sample Response

{
  "status": 200,
  "message": "Success",
  "data": {
    "pageNo": 1,
    "pageSize": 30,
    "lastPage": 1,
    "totalNumberOfItems": 1,
    "content": [
      {
        "id": "xxx-xxx-xxx",
        "merchantReference": "order-58213",
        "status": "COMPLETED",
        "customerName": "Jane Doe",
        "customerEmail": "jane.doe@example.com",
        "cardTokenId": "xxx-xxx-xxx",
        "expiresAt": "2026-09-12 15:30:00",
        "createdDate": "2026-09-12 15:05:22",
        "businessName": "Acme Technologies Ltd",
        "merchantName": "Acme Technologies Ltd",
        "businessId": "xxx-xxx-xxx"
      }
    ]
  }
}

cardTokenId is null until the link completes — combine hasCardToken=true with a createdFrom/createdTo range if, for example, you want to reconcile which of last week's links actually produced a usable token.

Webhook Notification

When a link completes successfully, PayOnUs sends a notification to your configured webhook URL (see Webhook Notifications for how to set this up):

{
  "event": "card_tokenization_link.completed",
  "cardTokenizationLinkId": "xxx-xxx-xxx",
  "cardTokenId": "xxx-xxx-xxx",
  "businessId": "xxx-xxx-xxx",
  "status": "COMPLETED"
}

This notification is currently only sent for successful completions, is delivered once, and is not retried if your endpoint is unreachable. We recommend also checking link status via the API above (or the search endpoint below) rather than relying on the webhook alone, especially for anything time-sensitive.

Search Card Tokens

Look up previously created card tokens for your business.

  • Path: /api/v1/card-tokens
  • Method: GET

Query Parameters (all optional except where noted)

Parameter Type Description
merchantId string Your Merchant ID. Required for merchant API-key access.
businessIds array One or more Business IDs to restrict the search to (defaults to all your businesses if omitted)
id string A specific card token's ID
requestId string The internal request ID recorded against the token
merchantReference string Your merchantReference from the tokenization link that produced the token
customerEmail string Filter by the customer's email
createdFrom date Only tokens created on/after this date (yyyy-MM-dd)
createdTo date Only tokens created on/before this date (yyyy-MM-dd)
pageNo number Page number, starting at 1 (default: 1)
pageSize number Results per page (default: 30)

Sample Response

{
  "status": 200,
  "message": "Success",
  "data": {
    "pageNo": 1,
    "pageSize": 30,
    "lastPage": 1,
    "totalNumberOfItems": 1,
    "content": [
      {
        "id": "xxx-xxx-xxx",
        "maskedPan": "512345******1234",
        "accountNumber": "1234567890",
        "tokenExpiry": "2030-01-01",
        "paymentGateway": "WEMABANK",
        "requestId": "ONUS-CTOKEN-...",
        "customerName": "Jane Doe",
        "customerEmail": "jane.doe@example.com",
        "merchantReference": "order-58213",
        "createdDate": "2026-09-12 15:05:22",
        "businessName": "Acme Technologies Ltd",
        "merchantName": "Acme Technologies Ltd",
        "businessId": "xxx-xxx-xxx"
      }
    ]
  }
}

merchantReference is populated when the token came from a consent link; it's null for any legacy tokens created before this flow existed.

Charging a Tokenized Card

Once you have a card token — from a completed consent link, or found via Search Card Tokens — you can charge it directly. Because the cardholder already went through consent and card capture once, there's no OTP, PIN, or 3DS step for the customer to complete this time: the charge is attempted and you get a final status straight away.

Request

  • Path: /api/v1/card-payments/with-token
  • Method: POST

Request Body Table

Field Type Required Description
businessId string Yes Your Business ID. Must be the same business the card token belongs to.
cardTokenId string Yes The id of a previously created card token (the id field from Search Card Tokens or the tokenization result).
amount number Yes Amount to charge. Minimum 1.
reference string Yes Your own unique reference for this charge. Must not already be in use for this business.
narration string No Optional transaction narration
notificationUrl string No Optional webhook override for this transaction. Notification is sent to the URL configured on the portal if not set.

Sample Request

{
  "businessId": "{business_id}",
  "cardTokenId": "{card_token_id}",
  "amount": 5000,
  "reference": "charge-order-58213-1",
  "narration": "Subscription renewal"
}

Sample Response

{
  "status": 200,
  "message": "Success",
  "data": {
    "authTypeRequired": null,
    "paymentStatus": "SUCCESSFUL",
    "onusReference": "ONUS-TKPY-2570434237835-03082025-103058",
    "redirectUrl": null,
    "message": "Card charged successfully",
    "descriptor": null,
    "processorRedirectHtmlOrUrl": null
  }
}

Unlike the regular Card Payments response, authTypeRequired and redirectUrl are always null here — a tokenized charge never requires a completion step. Use the returned onusReference with Verify Single Payment to check status later, the same way you would for any other card payment.

Possible Errors

Status Meaning
404 Card token not found
403 Card token does not belong to the businessId supplied
409 reference has already been used for this business
410 Card token has been deleted (see Delete a Card Token)
422 Card token has expired
422 Business not profiled on the NGN card gateway (production only)

Delete a Card Token

Permanently revoke a stored card. This deletes the token at the payment gateway as well as marking it deleted on our side — it can never be charged again afterward, and it stops appearing in Search Card Tokens results.

Request

  • Path: /api/v1/card-tokens/{id}
  • Method: DELETE

Parameters

Parameter Type Required Description
id string Yes The card token's ID (path parameter)
businessId string Yes Your Business ID (query parameter)

Sample Response

{
  "status": 200,
  "message": "Success",
  "data": {
    "message": "Card token deleted successfully"
  }
}

Calling delete again on a token you've already deleted is safe — it returns the same success shape ("message": "Card token already deleted") rather than an error, and does not attempt to contact the gateway a second time.

Possible Errors

Status Meaning
404 Card token not found
403 Card token does not belong to the businessId supplied

Notes

  • You never handle card data. Unlike a direct card payment integration, there's no encryption or card-capture form to build for this flow — the hosted consent page takes care of both.
  • The environment (sandbox vs. production) is determined automatically by the businessId you pass — there's nothing to configure on your end.
  • Consent links are single-use and expire (30 minutes by default, up to 24 hours). Plan your customer messaging accordingly — a link a customer opens after it has expired will show them a clear "link has expired" message and you'll need to generate a new one.
  • Use the Testing Your Integration guide and the test cards already documented for Card Payments to complete a consent link end-to-end in sandbox.