Card Issuing
Card Sandbox Simulation
Due to the complexity of card issuing, Column supports end-to-end card program testing in Sandbox from creating the program, to simulating an authorization, a capture, and a refund against a card.
Prerequisites
To simulate card transactions in Sandbox, start by creating a non-root entity. Debit card programs require a bank account for each non-root entity. Charge and credit card programs require a loan object for each non-root entity. Cardholder-secured charge card programs require a secured loan for each non-root entity, backed by a collateral bank account that holds the cardholder's deposit. Before simulating card transactions, a bank account must have a non-zero balance. A loan object must have a non-zero maximum principal balance and available credit.
If you plan to test real-time decisioning, please review webhook setup requirements as well prior to simulating card transactions. You can also simulate card transactions without real-time decisioning as described below.
Card Program
In Production, Column will configure a card program on your behalf. However, in Sandbox you can create a card program with POST /simulate/issuing/card-programs specifying the program type.
curl https://api.column.com/simulate/issuing/card-programs \
-XPOST \
-H 'Content-Type: application/json' \
-u :<YOUR API KEY> \
--data '{
"type": "debit",
"scheme": "visa",
"description": "sandbox card program"
}'typedebit, credit, or charge.schemevisa. Defaults to visa.credit_policyunsecured, cardholder_secured, or platform_secured. Defaults to unsecured. Ignored for debit programs.descriptionThe response is a card program object. Sandbox assigns and activates a BIN range for you, which allows cards created under the program to be resolved from simulated network messages.
Card Authorization Policies
Once a card program has been created, you can create card authorization policies. Policies are evaluated before real-time decisioning, so they are the cheapest way to enforce baseline merchant and velocity rules.
Create a policy and its first version with POST /issuing/card-authorization-policies.
curl https://api.column.com/issuing/card-authorization-policies \
-XPOST \
-H 'Content-Type: application/json' \
-u :<YOUR API KEY> \
--data '{
"scope": "card_program",
"effective_on": "<RFC 3339 timestamp at least 5 minutes from now>",
"config": {
"blocked_merchant_category_codes": ["7995"],
"allowed_merchant_country_codes": ["US"],
"card_spend_policies": [
{
"scope": "card_program",
"interval": "1_day",
"total_volume": 100000,
"number_of_transactions": 20
}
]
}
}'scopecard_program, card_account, or card.effective_onRFC 3339 timestamp. Offsets are converted to UTC.To change authorization rules later, add a new version to the existing policy with POST /issuing/card-authorization-policy-versions,
passing the card_authorization_policy_id along with the same scope, a new effective_on, and the new config.
The active policy version is the one with the most recent effective_on that has already passed. You must set effective_on at least 5 minutes in the future, so wait for the version to become effective before you simulate transactions against it.
A policy is only evaluated once it is attached to a card program, card account, or card. Attach a card_program-scoped policy by updating your card program with
PATCH /issuing/card-programs/:card_program_id:
curl https://api.column.com/issuing/card-programs/cpgm_3J6EP7ebC8vIwpP6lQfvy371JFQ \
-XPATCH \
-H 'Content-Type: application/json' \
-u :<YOUR API KEY> \
--data '{
"card_authorization_policy_id": "caup_2x8gszy5folpA9s0TOCseE9ABDM"
}'card_account-scoped and card-scoped policies are attached by passing card_authorization_policy_id when you create
the card account or card, or later by updating the card account with
PATCH /issuing/card-accounts/:card_account_id or updating the card with PATCH /issuing/cards/:card_id.
Card Accounts
You can now create card accounts under your card program with applicable card authorization policies. For a debit program, the card account links to the bank account that funds it. For a credit program, the card account links to the line of credit that funds it. See Card Account Types for how each program type links its card accounts.
curl https://api.column.com/issuing/card-accounts \
-XPOST \
-H 'Content-Type: application/json' \
-u :<YOUR API KEY> \
--data '{
"card_program_id": "cpgm_3J6EP7ebC8vIwpP6lQfvy371JFQ",
"bank_account_id": "bacc_2wQFHhraSXePmI1rC2uhnodtvh2"
}'card_program_idbank_account_idloan_idcardholder_secured program, the loan must have type=secured; its collateral bank account is derived from the loan and cannot be overridden.card_authorization_policy_idCards
Next, create a card. Virtual cards are the fastest path to testing in Sandbox as shipping details are not required.
curl https://api.column.com/issuing/cards \
-XPOST \
-H 'Content-Type: application/json' \
-u :<YOUR API KEY> \
--data '{
"type": "virtual",
"card_account_id": "cacc_3J6FHczL9wF2o3xsoVbOxK2Zu60",
"authorized_user_entity_id":"enti_3BPEJh4QweINlzZxBH1oZ94qrAQ",
"status":"active"
}'typevirtual or physical.card_account_idcard_authorization_policy_idcard_template_idexpiration_month / expiration_yearstatusencrypted_pingenerate_pintrue has Column set a random PIN, which the cardholder sees through PIN reveal. Can't be combined with encrypted_pin.The PAN and CVV2 are under PCI scope and are not returned when you create a card. You do not need them to simulate transactions — the simulate endpoints address the card by its card_id. If you are building a cardholder-facing card display, show card details with Column's card reveal SDKs for the web and React Native. Your server calls POST /issuing/cards/:card_id/reveal-tokens to create a single-use, short-lived reveal token, and the SDK uses that token to display the card details to the cardholder, so the card number and CVV2 never pass through your systems.
PIN Encryption
A card doesn't need a PIN. Pass encrypted_pin to set one when you create the card, or leave it out to create the card without a PIN. A card without a PIN has pin_status set to unset; it works for transactions that don't require a PIN, and transactions that do decline with pin_not_set until you set one. Renewals and replacements that keep the card number carry over the original card's PIN when encrypted_pin is omitted; replacements that issue a new card number are created without a PIN. See PINs on renewed and replaced cards. The PIN is the customer's chosen 4-digit code, encrypted with RSA-2048, OAEP padding, SHA-256 hash, then base64-encoded.
Encrypt the PIN on the cardholder's device, in your mobile app or web frontend, as soon as they enter it. Send only the base64 ciphertext to your servers and on to Column. Never send a plain-text PIN to your backend or encrypt it there, so the PIN never appears in your systems or logs. Don't store or reuse an encrypted PIN beyond the request that sets it.
Sandbox public key:
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEApBDtgxhJhZCmbvXWr7Zz
xs8u9JrSVWjtIDcIsD+5cfyv0OsWgjTxBIoBhUWeoTMizEGWWmLl9l0y/fWXFar3
wTriE+sun/cdFydCaBzB0nlyVqH9lIoKhsmpZUOsBS4Wrsnfb6GBzWFNlGu9ZkQE
+sXAEpf5G6gWKIoU6RACbslt0h79xW/EIeagOyl6MCW8Jtb8DsYwItGpVwoPPfU2
1XzmHBgeGTUOtRAWT1Ky//t5P9jcd5THqZjLBUqIq3K9Rz7OL/uY2oNFy57hdSWv
dXd0gnSdFKxOH6BrgTdUXkOU/ytdweOIcRgSGDVYKEHQuZa93XJirH3AY+Xk21CU
kwIDAQAB
-----END PUBLIC KEY-----This single key serves every sandbox platform on Column. It works only against test_-prefixed API credentials — production uses a different key issued during onboarding.
To set this on an already created card, update the card's PIN with PATCH /issuing/cards/:card_id/pin:
curl https://api.column.com/issuing/cards/card_3J6FHczL9wF2o3xsoVbOxK2Zu60/pin \
-XPATCH \
-H 'Content-Type: application/json' \
-u :<YOUR API KEY> \
--data '{
"encrypted_pin": "<base64 ciphertext>"
}'The API never returns a card's PIN. If PIN reveal is enabled for the card program, the cardholder can see it through
the card reveal SDKs. To have the cardholder confirm their current PIN
before changing it, also pass
current_encrypted_pin, encrypted the same way as encrypted_pin. The PIN only changes if it matches the card's
current PIN; otherwise the request fails with current_pin_incorrect and the PIN is unchanged. Sending
current_encrypted_pin for a card with no PIN fails with invalid_field_value. Column doesn't limit attempts, so
limit them on your side.
Treat an encrypted PIN like the PIN itself: never log it, store it, or reuse it. The current-PIN check only proves
that the caller has the encrypted PIN, not that they know the PIN. If you keep the encrypted_pin from an earlier
request, anyone who gets hold of it, such as someone who takes over the cardholder's account with you, could replay
it as current_encrypted_pin to pass the check and set a new PIN.
Activate Card
A card must be active before it will authorize. If the card was created in a paused state, activate it:
curl https://api.column.com/issuing/cards/card_3J6Gg2WgNLoZAz67PrHhJy9aHlT/activate \
-XPATCH \
-u :<YOUR API KEY>Simulate Card Transaction
With a funded, active card you can drive the full network message lifecycle. Every simulate endpoint returns the
resulting card transaction object, so you can inspect amounts and events after each step.
POST /simulate/issuing/cards/:card_id/card-transactions/authorizationPOST /simulate/issuing/card-transactions/:card_transaction_id/incremental-authorizationPOST /simulate/issuing/card-transactions/:card_transaction_id/capturePOST /simulate/issuing/card-transactions/:card_transaction_id/reversalPOST /simulate/issuing/card-transactions/:card_transaction_id/refundPOST /simulate/issuing/cards/:card_id/card-transactions/financialPOST /simulate/issuing/cards/:card_id/threeds/challengeAll amounts are expressed in the smallest unit of the merchant's currency and must be greater than 0 and no more
than 999999999999.
Authorization
Authorization places a hold on the card account. It is the entry point for exercising both authorization policies and real-time decisioning.
curl https://api.column.com/simulate/issuing/cards/card_3J6Gg2WgNLoZAz67PrHhJy9aHlT/card-transactions/authorization \
-XPOST \
-H 'Content-Type: application/json' \
-u :<YOUR API KEY> \
--data '{
"cardholder_amount": 2500,
"merchant_currency": "USD",
"realtime_decision":"approve"
}'cardholder_amountmerchant_currencycardholder_fx_rateUSD onlyUSD, the cardholder billing currency.merchant_category_code5812 (restaurants) or 6011 (ATM). Defaults to 5999.merchant_nameACQUIRER NAME.merchant_cityCITY NAME.merchant_country_codeUS.realtime_decisionapprove, decline, or partially_approve.realtime_decision_reasonrealtime_decision is decline. Defaults to generic_decline.realtime_partially_approved_amountrealtime_decision is partially_approve. In USD, the cardholder billing currency. Must be greater than 0 and less than the billed amount (cardholder_amount, converted at cardholder_fx_rate for non-USD merchants).realtime_funding_account_idrealtime_decision is approve or partially_approve.network_standinapproved or declined.network_decision_reasonnetwork_standin is declined.three_ds_attempt_idauthenticated 3DS attempt on the same card, producing authentication details with liability shift. See 3DS Authentication.cardholder_input.pinentry_modeswipe, keyed or ecommerce. Defaults to swipe for physical cards and ecommerce for virtual cards. See Testing entry modes.cardholder_input.cvv2entry_mode keyed or ecommerce; defaults to the card's real CVV2. See Testing CVV2.cardholder_input.expiration_date{"month": 12, "year": 2029}. See Testing expiration dates.cardholder_input.billing_addressstreet and postal_code the cardholder enters, checked by address verification (AVS). See Testing address verification.Use the merchant fields to exercise authorization policy and real-time decisioning rules that depend on the merchant
category or country. For example, with the authorization policy created above, an authorization with merchant_category_code set to 7995 is
declined before it reaches your decisioning endpoint.
Testing your real-time decisioning endpoint. When you omit both realtime_decision and network_standin, the simulator sends a real decision webhook to the endpoint your platform has subscribed to decision.issuing.card_transaction and waits for your response. If no destination is configured, the authorization declines. Set realtime_decision instead when you want to exercise a specific outcome without standing up an endpoint. See Real-time Decisioning for the full decisioning flow.
realtime_decision and network_standin are mutually exclusive — sending both is rejected. network_standin produces a transaction with decision_source set to card_network, which is how you can simulate an issuer timeout.
The response contains the new card_transaction_id (prefixed ctxn_) used by the remaining steps, and emits issuing.card_transaction.created.
Testing PINs
To simulate a cardholder typing a PIN at the terminal, pass it in cardholder_input.pin on an authorization or a
single-message financial for a physical card. The simulator encrypts it the way a terminal would, and Column checks it with the same PIN verification
and lockout as in Production:
curl https://api.column.com/simulate/issuing/cards/card_3J6Gg2WgNLoZAz67PrHhJy9aHlT/card-transactions/authorization \
-XPOST \
-H 'Content-Type: application/json' \
-u :<YOUR API KEY> \
--data '{
"cardholder_amount": 2500,
"merchant_currency": "USD",
"realtime_decision": "approve",
"cardholder_input": {"pin": "1234"}
}'decisioning_details on the first eventdecision declined, decision_reason pin_invalid, decision_source column.decision_reason pin_not_set.decision_reason pin_blocked, whatever the PIN.Three wrong PINs in a row block the card's PIN. Unblock it with
POST /issuing/cards/:card_id/pin/unblock to test again. A PIN on a virtual card is rejected with 400, since a
virtual card has no terminal.
Testing entry modes
Set entry_mode to choose how the card is presented to the merchant, so you can test card-present and
card-not-present decisioning. It sets the PAN entry mode and POS condition code on the simulated network message, and
the authorization's point_of_sale_details reflect it:
entry_modepan_entry_modecard_presenceterminal_typeswipeswipepresentattendedkeyedmanual_entrynot_presentunknownecommercemanual_entrynot_presentecommerceA physical card supports all three and defaults to swipe. A virtual card only supports ecommerce, its default. A
PIN in cardholder_input.pin can be entered with swipe or keyed, but not with ecommerce, which has no PIN pad.
These combinations are rejected with 400.
Chip and contactless entry are not supported: they need an EMV cryptogram that the simulator cannot generate under sandbox card keys.
Testing CVV2
A keyed or online transaction carries the card's real CVV2. To simulate a cardholder typing a different CVV2, pass it in
cardholder_input.cvv2 with entry_mode keyed or ecommerce, a virtual card's default. Column checks it with the
same CVV2 verification as in Production:
curl https://api.column.com/simulate/issuing/cards/card_3J6Gg2WgNLoZAz67PrHhJy9aHlT/card-transactions/authorization \
-XPOST \
-H 'Content-Type: application/json' \
-u :<YOUR API KEY> \
--data '{
"cardholder_amount": 2500,
"merchant_currency": "USD",
"realtime_decision": "approve",
"entry_mode": "ecommerce",
"cardholder_input": {"cvv2": "123"}
}'decisioning_details on the first eventdecision declined, decision_reason invalid_cvv2, decision_source column.A CVV2 with entry_mode swipe, a physical card's default, is rejected with 400, since the cardholder doesn't enter a
CVV2 when the card is swiped. On a single-message financial, which has no entry_mode, a
CVV2 is only supported on a virtual card.
Testing expiration dates
To simulate the expiration date the cardholder presents, pass it in cardholder_input.expiration_date with a month
(1 to 12) and a 4-digit year (2000 to 2068). Column compares it with the card's expiration_month and
expiration_year, as it does in Production:
curl https://api.column.com/simulate/issuing/cards/card_3J6Gg2WgNLoZAz67PrHhJy9aHlT/card-transactions/authorization \
-XPOST \
-H 'Content-Type: application/json' \
-u :<YOUR API KEY> \
--data '{
"cardholder_amount": 2500,
"merchant_currency": "USD",
"realtime_decision": "approve",
"cardholder_input": {"expiration_date": {"month": 12, "year": 2029}}
}'decisioning_details on the first eventdecision declined, decision_reason invalid_expiration, decision_source column.Without expiration_date, the simulated transaction carries no expiration date and Column skips the check. A card
past its own expiration declines with card_expired whatever date is presented. The same field and outcomes apply to a
single-message financial.
Testing address verification
To simulate a cardholder entering a billing address, for example at checkout or at a fuel pump, pass it in
cardholder_input.billing_address on an authorization or a single-message financial. The
simulator sends it in the Visa address verification (AVS) fields, and Column compares each field with the address of
the card's authorized user entity, as in Production:
curl https://api.column.com/simulate/issuing/cards/card_3J6Gg2WgNLoZAz67PrHhJy9aHlT/card-transactions/authorization \
-XPOST \
-H 'Content-Type: application/json' \
-u :<YOUR API KEY> \
--data '{
"cardholder_amount": 2500,
"merchant_currency": "USD",
"realtime_decision": "approve",
"cardholder_input": {"billing_address": {"street": "123 Main St", "postal_code": "94105"}}
}'The results appear in the transaction's cardholder_verification_details:
street_address_verification_status / postal_code_verification_statusverifiedinvalidnot_providedThe street matches on its digits (the house number, plus any unit number), so 123 Main St matches 123 Main Street.
A 5-digit ZIP matches a ZIP+4 on file with the same first 5 digits; send a ZIP+4 as 9 digits without the hyphen. AVS is
informational: a mismatch doesn't decline the transaction, and your real-time decisioning can read the results.
street takes 1 to 40 printable ASCII characters and postal_code 1 to 9 letters, digits, spaces or hyphens; anything
else is rejected with 400.
Incremental Authorization
Incremental authorization adds to an existing hold, commonly seen with hotels, gas stations, and rideshare companies.
curl https://api.column.com/simulate/issuing/card-transactions/ctxn_3J6f3h8oQ6J0SWTTNQOdEiy43pg/incremental-authorization \
-XPOST \
-H 'Content-Type: application/json' \
-u :<YOUR API KEY> \
--data '{
"cardholder_amount": 500
}'cardholder_amountfx_rateThe original transaction must have started as an authorization and must not have captured funds. Currency codes carry
over from the original authorization. Each increment sends a real-time decision request to your endpoint subscribed to
decision.issuing.card_transaction, and declines if none is configured.
Capture
Capture clears the transaction and moves funds. By default a capture is final and releases the entire remaining hold. A capture will not succeed if there is no authorization hold to capture.
curl https://api.column.com/simulate/issuing/card-transactions/ctxn_3J6f3h8oQ6J0SWTTNQOdEiy43pg/capture \
-XPOST \
-H 'Content-Type: application/json' \
-u :<YOUR API KEY> \
--data '{
"cardholder_amount": 2500
}'cardholder_amountis_partial_capturetrue, keeps the remaining hold active for future captures (split shipment). Defaults to false.On a final capture, over-capture is permitted so you can simulate tips, hotel incidentals, and fuel charges. On a partial capture the amount must not exceed the remaining authorized amount.
Each capture appends a clear event to the card transaction and emits issuing.card_transaction.updated. See
Card Transactions for how the six amount types roll up across events — negative
amounts are funds held or withdrawn from the card account, positive amounts are funds released or deposited to the card account.
Single-Message Financial
Single-message financial simulates a transaction that authorizes and clears in a single network message, so there is no authorization hold before capture. Examples include ATM transactions and debit purchases.
curl https://api.column.com/simulate/issuing/cards/card_3J6FHczL9wF2o3xsoVbOxK2Zu60/card-transactions/financial \
-XPOST \
-H 'Content-Type: application/json' \
-u :<YOUR API KEY> \
--data '{
"cardholder_amount": 4000,
"merchant_currency": "USD",
"realtime_decision":"approve"
}'cardholder_amountmerchant_currencycardholder_fx_rateUSD onlyUSD, the cardholder billing currency.merchant_category_code5999.merchant_nameACQUIRER NAME.merchant_cityCITY NAME.merchant_country_codeUS.realtime_decisionapprove, decline, or partially_approve.realtime_decision_reasonrealtime_decision is decline. Defaults to generic_decline.realtime_partially_approved_amountrealtime_decision is partially_approve. In USD, the cardholder billing currency. Must be greater than 0 and less than the billed amount (cardholder_amount, converted at cardholder_fx_rate for non-USD merchants).realtime_funding_account_idrealtime_decision is approve or partially_approve.cardholder_input.pincardholder_input.cvv2cardholder_input.expiration_date{"month": 12, "year": 2029}. See Testing expiration dates.cardholder_input.billing_addressstreet and postal_code the cardholder enters, checked by address verification (AVS). See Testing address verification.PIN debit purchases are usually single-message financials. To simulate one, pass the PIN in cardholder_input.pin.
The PIN outcomes are the same as on an authorization. Setting merchant_category_code to 6011 puts
the transaction at an ATM merchant category, but the simulator always sends a purchase, so the transaction's type is
purchase rather than cash_withdrawal.
As with an authorization, when you omit realtime_decision the simulator sends a real decision webhook to the endpoint
your platform has subscribed to decision.issuing.card_transaction, and declines if no destination is configured. A
partially approved financial transaction settles only the approved amount. Card status, authorization policy, and
balance checks run first and can decline a financial transaction before it reaches your endpoint. There is no
network_standin field for financial transactions.
Reversal
Reversal simulates an acquirer reversing the message that opened a transaction, such as a voided sale or a terminal that never received its authorization response. The transaction must have started as an authorization or a single-message financial message.
curl https://api.column.com/simulate/issuing/card-transactions/ctxn_3J6f3h8oQ6J0SWTTNQOdEiy43pg/reversal \
-XPOST \
-H 'Content-Type: application/json' \
-u :<YOUR API KEY> \
--data '{
"cardholder_amount": 1000
}'cardholder_amountOmit cardholder_amount to reverse in full: an authorization releases whatever hold remains, including incremental
authorizations, and a financial transaction reverses its settled amount. Partial reversals only apply to authorizations.
Reversals act on the outstanding hold, so a transaction that has already been captured has nothing left to reverse —
refund it instead.
Refund
Refund returns funds to the card account against a previously captured transaction.
curl https://api.column.com/simulate/issuing/card-transactions/ctxn_3J6f3h8oQ6J0SWTTNQOdEiy43pg/refund \
-XPOST \
-H 'Content-Type: application/json' \
-u :<YOUR API KEY> \
--data '{
"cardholder_amount": 2500
}'Each refund creates a new card transaction linked to the purchase by original_card_transaction_id. The total refunded
must not exceed the settled amount on the original transaction.
3DS Authentication
3DS authentication simulates a 3DS authentication attempt for an online purchase. Link a successful attempt to an authorization with
three_ds_attempt_id to test a liability shift.
curl https://api.column.com/simulate/issuing/cards/card_3J6Gg2WgNLoZAz67PrHhJy9aHlT/threeds/challenge \
-XPOST \
-H 'Content-Type: application/json' \
-u :<YOUR API KEY> \
--data '{
"amount": 2500,
"currency": "USD",
"realtime_decision": "challenge"
}'amountcurrencyrealtime_decisionfrictionless_approve, challenge, or decline_authentication.decline_reasonsuspected_fraud or transaction_not_permitted when realtime_decision is decline_authentication. Defaults to suspected_fraud.message_categorypayment or non_payment (for example, adding a card to a merchant's file).merchant_name / merchant_url / merchant_category_code / merchant_country_codeWhen you omit realtime_decision, the simulator sends a decision request to your endpoint subscribed to
decision.issuing.card_authentication. If none is subscribed, it creates an OTP challenge.
frictionless_approveauthenticates the attempt and emitsissuing.3ds.authentication_completed.challengestarts an OTP challenge and emitsissuing.3ds.otp_generated.decline_authenticationrejects the attempt and emitsissuing.3ds.authentication_completed.
Complete the challenge with POST /simulate/issuing/threeds/:three_ds_attempt_id/complete, setting outcome to
successful or failed. A successful outcome leaves the attempt authenticated, ready to pass as
three_ds_attempt_id on a simulated authorization.
curl https://api.column.com/simulate/issuing/threeds/<three_ds_attempt_id>/complete \
-XPOST \
-H 'Content-Type: application/json' \
-u :<YOUR API KEY> \
--data '{
"outcome": "successful"
}'Simulate Card Tokens
You can simulate the digital wallet provisioning flow and network token lifecycle events for cards on a card program. Tokens are only created through the network provisioning flow, so in Sandbox you start by simulating a wallet's eligibility check. See Card Tokens for token statuses, events, and provisioning decisioning.
POST /simulate/issuing/cards/tokens/check-eligibilityPOST /simulate/issuing/cards/tokens/:card_token_id/approve-provisioningPOST /simulate/issuing/cards/tokens/:card_token_id/step-up/methodsPOST /simulate/issuing/cards/tokens/:card_token_id/step-up/send-passcodePOST /simulate/issuing/cards/tokens/:card_token_id/step-up/completePOST /simulate/issuing/cards/tokens/:card_token_id/device-bindingPOST /simulate/issuing/cards/tokens/:card_token_id/suspendPOST /simulate/issuing/cards/tokens/:card_token_id/resumePOST /simulate/issuing/cards/tokens/:card_token_id/deactivateEligibility Check
Eligibility check simulates a digital wallet checking whether a card can be tokenized. An eligible card returns a new card token in
pending_activation status; an ineligible card returns an error with the reason.
curl https://api.column.com/simulate/issuing/cards/tokens/check-eligibility \
-XPOST \
-H 'Content-Type: application/json' \
-u :<YOUR API KEY> \
--data '{
"card_id": "card_3J6Gg2WgNLoZAz67PrHhJy9aHlT"
}'Approve Provisioning
Approve provisioning simulates the wallet's provisioning request for the pending_activation token returned by the eligibility check.
curl https://api.column.com/simulate/issuing/cards/tokens/<card_token_id>/approve-provisioning \
-XPOST \
-H 'Content-Type: application/json' \
-u :<YOUR API KEY> \
--data '{
"wallet_type": "apple_pay",
"device_type": "mobile_phone",
"device_name": "Cardholder iPhone",
"action_code": "approve"
}'action_codeapprove, decline, or step_up.wallet_typeapple_pay, google_pay, or samsung_pay.device_typemobile_phone, tablet, or watch.device_nametoken_requestor_nametoken_typewallet or card_on_file. Defaults to wallet.Testing your provisioning decisioning endpoint. When you omit action_code, the request goes through Column's real
provisioning decisioning, including your endpoint subscribed to decision.issuing.card_token. An approve decision
activates the token and emits issuing.card_token.activated.
Step-up Verification
A step_up decision leaves the token ready for cardholder verification. If the cardholder has contact info on file,
the simulator also runs the retrieve-methods and send-passcode steps for you, so you only need to complete the step-up.
Otherwise, or to choose the delivery method yourself, run the steps individually:
- Retrieve methods returns the SMS and email methods available from the cardholder's contact info.
- Send passcode emits
issuing.card_token.send_passcodewith a simulated one-time passcode and moves the token topending_authentication. Pass one of the returned method identifiers asotp_method_identifier, or omit it to select one automatically. - Complete finishes verification.
curl https://api.column.com/simulate/issuing/cards/tokens/<card_token_id>/step-up/complete \
-XPOST \
-H 'Content-Type: application/json' \
-u :<YOUR API KEY> \
--data '{
"outcome": "success"
}'An outcome of success activates the token and emits issuing.card_token.activated; failure moves it to
activation_denied and emits issuing.card_token.activation_denied. The token must be in pending_authentication.
Device Binding
Device binding simulates binding an existing token to a new device and returns the updated card token.
curl https://api.column.com/simulate/issuing/cards/tokens/<card_token_id>/device-binding \
-XPOST \
-H 'Content-Type: application/json' \
-u :<YOUR API KEY> \
--data '{
"device_type": "watch",
"device_name": "Cardholder Watch",
"decision_override": "approve"
}'decision_overrideapprove, decline, or step_up. Omit to call your endpoint subscribed to decision.issuing.card_token.device_binding.device_iddevice_typemobile_phone, tablet, or watch.device_namewallet_account_idNetwork Lifecycle Events
Simulate the card network changing a token's status, exactly as a production network notification would, with suspend, resume, and deactivate. None of these endpoints require a request body.
suspendpaused and emits issuing.card_token.paused.resumeactive and emits issuing.card_token.activated.deactivatedeactivated and emits issuing.card_token.deactivated. Deactivating a token that is already deactivated returns it unchanged without emitting an event.To pause, activate, or deactivate a token from your own platform instead, use the card token endpoints.