Skip to content

How to Create a Card

This guide walks through the complete process of issuing a card using the QPay Issuer API — from importing your HSM certificate to monitoring the card through to the ACTIVE state. It is intended for developers at issuer organizations who have already set up their sandbox tenant and obtained client credentials.

Time to complete: ~20 minutes
API: Issuer API — base URL https://sandbox.qpay.quecto.com.br


Prerequisites

  • An access token for an issuer tenant. See Authentication.
  • Access to an HSM or cryptographic library capable of generating an ECC P-256 key pair and a PIN block. The QPay platform never handles PINs in plaintext — all PIN material must be encrypted before being sent.

Set your token in a shell variable for convenience:

export TOKEN="eyJhbGci..."
export BASE="https://sandbox.qpay.quecto.com.br"

Overview

flowchart TD
    A[Import certificate] --> B[Get HSM public key]
    B --> C[Select data prep template]
    C --> D[Select embosser]
    D --> E[Create card layout]
    E --> F[Create card product]
    F --> G[Create card]
    G --> H[Monitor status]

Step 1 — Import a certificate

QPay uses your issuer certificate to establish trust for cryptographic operations (ARQC validation, ARPC generation) during transaction authorization. You must import a certificate before issuing any cards.

Generate an ECC P-256 certificate signing request (CSR) with your HSM or openssl:

openssl ecparam -name prime256v1 -genkey -noout -out issuer.key
openssl req -new -key issuer.key -out issuer.csr \
  -subj "/CN=My Issuer/O=My Organization/C=BR"

# Base64-encode the CSR (single line, no line breaks)
CSR_B64=$(base64 -w 0 issuer.csr)

Submit the CSR:

curl -s -X POST $BASE/issuer/certificates \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d "{\"certificate_request_pem\": \"$CSR_B64\"}" | tee cert.json

Example response:

{
  "certificate_id": 42,
  "hsm_key_reference": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "certificate_pem_base64": "LS0tLS1CRUdJTi..."
}

Save certificate_id and hsm_key_reference. Certificates are valid for 90 days — rotate before expiry to avoid authorization failures.

export CERT_ID=$(cat cert.json | jq -r '.certificate_id')
export HSM_KEY_REF=$(cat cert.json | jq -r '.hsm_key_reference')

Step 2 — Retrieve the HSM public key

QPay exposes an HSM public key that your system uses to encrypt the cardholder's PIN block before sending it in the card creation request. Retrieve it now so you have it ready for Step 7.

curl -s $BASE/issuer/key-agreement \
  -H "Authorization: Bearer $TOKEN" | jq .

Example response:

{
  "cert": "-----BEGIN CERTIFICATE-----\nMIIB....\n-----END CERTIFICATE-----\n"
}

PIN encryption is an HSM operation

Use the public key from this response to encipher the PIN block according to your HSM's key agreement protocol (typically ECDH + AES key wrap). This step is performed entirely within your secure environment — QPay never receives a plaintext PIN.


Step 3 — Select a data prep template

Data preparation templates are configured by your processor and define the card network parameters for your BIN range. List the templates available to your issuer:

curl -s $BASE/issuer/card-templates \
  -H "Authorization: Bearer $TOKEN" | jq .

Example response:

[
  {
    "id": 7,
    "name": "UnionPay Classic",
    "description": "Standard UnionPay debit template",
    "iin": "62270000",
    "applications": [...]
  }
]

Pick the template that matches your product and note its id:

export TEMPLATE_ID=7

Step 4 — Select an embosser

Embossers are the card personalization bureaus that physically produce your cards. List the embossers available to your issuer:

curl -s $BASE/issuer/embossers \
  -H "Authorization: Bearer $TOKEN" | jq .

Example response:

[
  {
    "embosser_id": 3,
    "name": "Acme Card Bureau"
  }
]

Note the embosser_id:

export EMBOSSER_ID=3

Step 5 — Create a card layout

A card layout defines the visual and physical design of the card that is sent to the embosser. Create one:

curl -s -X POST $BASE/issuer/card-layouts \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"layout": "standard-blue-v1"}' | jq .

Example response:

{
  "layout_id": 15
}
export LAYOUT_ID=15

Tip

The layout field value is a layout identifier string agreed upon with your embosser. Contact your embosser for the correct value — it maps to artwork and print specifications on their side.


Step 6 — Create a card product

A card product ties together a layout, a data prep template, and an embosser. It represents a specific card offering (e.g., "Classic Debit Card — Blue Design"):

curl -s -X POST $BASE/issuer/card-products \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d "{
    \"name\": \"Classic Debit\",
    \"description\": \"Standard UnionPay debit card\",
    \"layout_id\": $LAYOUT_ID,
    \"data_prep_template_id\": $TEMPLATE_ID,
    \"default_embosser_id\": $EMBOSSER_ID
  }" | jq .

Example response:

{
  "product_id": 101
}
export PRODUCT_ID=101

Once you have a product, you can reuse it for every card you issue under this offering — you do not need to repeat Steps 3–6 for each card.


Step 7 — Create the card

With a product in place, issue a card for a cardholder. This is the step where you supply the encrypted PIN block.

Prepare the PIN block first

Before calling this endpoint, your HSM must:

  1. Generate a PIN block for the cardholder's chosen PIN (ISO 9564 format 0 or 4)
  2. Encipher it using the QPay HSM public key retrieved in Step 2
  3. Produce the enciphered_pin_block (hex-encoded ciphertext) and the incoming_pek key reference
curl -s -X POST $BASE/issuer/cards/create \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d "{
    \"product_id\": $PRODUCT_ID,
    \"reference_id\": \"MY-UNIQUE-REF-001\",
    \"card_holder_name\": \"JOAO DA SILVA\",
    \"billing_address\": {
      \"name\": \"Joao da Silva\",
      \"postal_code\": \"01310-100\",
      \"country\": \"BR\",
      \"state\": \"SP\",
      \"city\": \"São Paulo\",
      \"address_line1\": \"Av. Paulista 1000\",
      \"address_line2\": \"Apto 42\"
    },
    \"shipping_address\": {
      \"name\": \"Joao da Silva\",
      \"postal_code\": \"01310-100\",
      \"country\": \"BR\",
      \"state\": \"SP\",
      \"city\": \"São Paulo\",
      \"address_line1\": \"Av. Paulista 1000\",
      \"address_line2\": \"Apto 42\"
    },
    \"enciphered_pin_block\": \"A1B2C3D4E5F60708\",
    \"incoming_pek\": {
      \"key_reference\": \"$HSM_KEY_REF\"
    }
  }" | jq .

Request fields reference:

Field Type Required Description
product_id integer Yes ID from Step 6
reference_id string Yes Your system's unique identifier for this card
card_holder_name string Yes Name to emboss on the card (uppercase)
billing_address object Yes Cardholder's billing address
shipping_address object Yes Delivery address for the physical card
enciphered_pin_block string Yes PIN block encrypted under the QPay HSM public key
incoming_pek.key_reference string Yes HSM key reference from Step 1
embosser_id integer No Override the product's default embosser

Example response:

{
  "reference_number": "QPY-20260602-001",
  "embosser_id": 3,
  "status": "PROVISIONED",
  "embossing_status": "pending",
  "card_id": 5821,
  "errors": []
}

The card is created with status PROVISIONED and enters the embossing queue.


Step 8 — Monitor card status

Track the card through its lifecycle using the card listing endpoint:

curl -s $BASE/issuer/cards \
  -H "Authorization: Bearer $TOKEN" | jq '.cards[] | select(.card_id == 5821)'

Card status lifecycle:

PROVISIONED  →  ACTIVE
              FROZEN  ←→  ACTIVE
              BLOCKED
Status Meaning
PROVISIONED Card created, awaiting embossing and delivery
ACTIVE Embossed and delivered — card is operational
FROZEN Temporarily suspended (reversible)
BLOCKED Permanently blocked

In the sandbox, the embossing step is simulated automatically and the card transitions to ACTIVE shortly after creation.


Issuing multiple cards (batch)

For bulk issuance, use the batch endpoint instead of calling /cards/create for each card individually:

POST $BASE/issuer/cards/create/batch/{product_id}

The request body takes a request_id (idempotency key) and a card_batch_info array where each entry has the same fields as a single card creation. The response includes a batch_id you can use to poll status:

GET $BASE/issuer/cards/create/batch/{product_id}/{batch_id}

See the Issuer API reference for the full batch request schema.