Payment Methods
A Payment Method is a saved card or bank account you can charge again. Cards are never sent to this API as numbers: the browser tokenizes the card with hlhunt.js (or the capture-session flow below) and your server stores the token. Bank accounts are sent as routing and account numbers to this endpoint from your server. Both kinds can be linked to a customer with customer_id. The legacy shape with type "PAYMENT_CARD" and raw card numbers that older pages showed is rejected with 400 pci_vault_required.
/v1/capture-sessionCreate a card capture session (browser)
Opens a short-lived capture endpoint in HL Hunt's PCI vault. This is what hlhunt.js calls for you; call it directly only if you are building your own browser or mobile tokenization. Publishable key (pk_live_ / pk_test_). CORS is open on this route.
Request body
| Field | Type | Required | Description |
|---|---|---|---|
| ttl | string | optional | Lifetime such as "5m", "10m", "1h". Default 10m, capped at 60m. Invalid formats are rejected with 400 invalid_param. |
| reference | string | optional | Your own reference, stored on the capture. |
Response
| Field | Type | Description |
|---|---|---|
| object | string | "capture_session". |
| capture_url | string | HTTPS URL the browser POSTs the card (or bank) fields to. |
| capture_secret | string | Send as the X-PCIVault-Capture-Secret header on that POST. |
| capture_id | string | Id of the capture endpoint. |
| expires_at | string | ISO 8601 expiry. |
| key_user | string | Vault key user; not needed for the normal flow. |
| livemode | boolean | Mode of the key that created the session. |
- HTTP 201. The browser then POSTs JSON { card_number, card_holder, cvv, expiry_month ("MM"), expiry_year ("YYYY"), expiry_year_short } — or { routing_number, account_number, account_type, account_holder } for a bank account — to capture_url and receives { token, reference }. Use token as source on POST /v1/charges (one-time) or as payment_token on POST /v1/payment-methods (save). See the hlhunt.js guide.
- 403 merchant_not_onboarded · 502 vault_error when the vault is unavailable.
- Legacy alternative: POST https://payments.hlhunt.org/api/tokenize/session with a secret key returns { tokenizeUrl, tokenSecret, tokenSecretHeader: "X-Tokenize-Secret", expiresAt }; the browser POSTs the same card fields to tokenizeUrl with that header and gets { token, reference }. It still works but hlhunt.js and /v1/capture-session are the supported path.
curl -X POST https://api.hlhunt.org/v1/capture-session \
-H "Authorization: Bearer pk_live_your_publishable_key" \
-H "Content-Type: application/json" \
-d '{ "ttl": "10m" }'/v1/payment-methodsSave a card (from a token)
Store a tokenized card for later charges. Accepts a secret or a publishable key. The token comes from hlhunt.js createToken or the capture-session flow; card.* fields are display data only and are stored as given.
Request body
| Field | Type | Required | Description |
|---|---|---|---|
| type | string | required | "card". |
| payment_token | string | required | The token returned by the browser capture. Legacy alias: vault_token. |
| token_reference | string | optional | The reference returned next to the token. Legacy alias: vault_reference. |
| card | object | optional | { last4, brand, exp_month, exp_year } for display. hlhunt.js returns these in result.card. Sending card.number or card.cvc is rejected with 400 pci_vault_required. |
| name | string | optional | Cardholder name. Default "Card". |
| customer_id | string | optional | Link the card to a customer of this business. Also triggers payment_method.saved. |
| merchant_id | string | optional | Multi-business accounts only. |
Response
| Field | Type | Description |
|---|---|---|
| id | string | Payment method id: pcivault_<token> (pcivault_test_<token> for test keys). Use it as source on POST /v1/charges. |
| object | string | "payment_method". |
| type | string | "card". |
| card | object | { brand, last4, exp_month, exp_year } — exactly the display values you sent, or null. |
| name | string | Cardholder name. |
| created | string | ISO 8601. |
- HTTP 201. 400 missing_required_param (type, payment_token) · 400 pci_vault_required (raw card data) · 400 invalid_type · 403 merchant_not_onboarded.
- The same card tokenizes to the same token, so saving it twice updates the existing record instead of creating a second one.
- Emits payment_method.created, and payment_method.saved when customer_id is set.
curl -X POST https://api.hlhunt.org/v1/payment-methods \
-H "Authorization: Bearer sk_live_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"type": "card",
"payment_token": "9f1c3b…token from hlhunt.js…",
"token_reference": "ref_from_hlhunt_js",
"name": "Jenny Rosen",
"customer_id": "c1m9k2x7e0001qz8f4h2b6n3p",
"card": { "last4": "4242", "brand": "visa", "exp_month": 12, "exp_year": 2029 }
}'/v1/payment-methodsSave a bank account (ACH)
Store a US bank account for ACH debits. Routing and account numbers are sent from your server to this endpoint. Since 2026-10-19 a bank account entered this way must be verified by a $0.01 microdeposit before its first debit: the response comes back with status requires_verification and the account owner submits the FNX code from their bank statement to POST /v1/payment-methods/{id}/verify.
Request body
| Field | Type | Required | Description |
|---|---|---|---|
| type | string | required | "bank_account". |
| bank_account.routing_number | string | required | 9-digit ABA routing number. |
| bank_account.account_number | string | required | Account number. |
| bank_account.account_type | string | optional | "checking" or "savings". Default checking. |
| name | string | optional | Account holder name. Default "Bank Account". There is no separate bank-name field; the holder is what is stored and shown. |
| customer_id | string | optional | Link the account to a customer. The instrument is then created on the buyer's own identity, never the merchant's. |
| merchant_id | string | optional | Multi-business accounts only. |
Response
| Field | Type | Description |
|---|---|---|
| id | string | Processor instrument id (e.g. PI…). Use it as source on POST /v1/charges and as {id} on …/verify. |
| object | string | "payment_method". |
| type | string | "bank_account". |
| bank_account | object | { last4, account_type }. |
| name | string | Account holder name. |
| status | string | requires_verification until the microdeposit code is accepted, then verified. |
| verification | object | Present while requires_verification: { method: "microdeposit", message } explaining the $0.01 deposit and the FNX code. |
| created | string | ISO 8601. |
- HTTP 201. 400 missing_required_param (routing or account number) · 400 bank_account_error when the processor rejects the account · 403 merchant_not_onboarded.
- Charging an unverified account answers 402 card_error with code bank_account_unverified; a permanently failed verification answers bank_account_verification_failed. Three code attempts per deposit, 30-day window.
- Emits payment_method.created.
curl -X POST https://api.hlhunt.org/v1/payment-methods \
-H "Authorization: Bearer sk_live_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"type": "bank_account",
"name": "Jenny Rosen",
"customer_id": "c1m9k2x7e0001qz8f4h2b6n3p",
"bank_account": {
"routing_number": "021000021",
"account_number": "9876543210",
"account_type": "checking"
}
}'/v1/payment-methods/{payment_method_id}/verifyVerify a bank account (microdeposit code)
Submit the code printed in the statement description of the $0.01 deposit (it starts with FNX). On success the bank account becomes chargeable.
Request body
| Field | Type | Required | Description |
|---|---|---|---|
| payment_method_id | string (path) | required | The bank account's id from POST /v1/payment-methods. |
| verification_code | string | required | The FNX code, e.g. FNX7Q2M. Case and spaces are normalised. |
Response
| Field | Type | Description |
|---|---|---|
| id / object / type | The payment method. | |
| status | string | "verified". |
| bank_account | object | { last4 }. |
- 400 missing_required_param · 400 not_a_bank_account · 404 resource_missing.
- 422 { error: { type: "invalid_request_error", code, message, attempts_remaining, terminal } } when the code is wrong or the deposit can no longer be verified. code is the processor's reason in lower case: incorrect_microdeposit_verification_code (retry, attempts_remaining tells you how many), microdeposit_verification_attempts_exceeded, microdeposit_verification_expired, microdeposit_returned, total_microdeposit_attempts_exceeded_account_locked, invalid_bank_account_routing_number (terminal: true — collect a different bank account), invalid_field (code not in FNX… form).
curl -X POST https://api.hlhunt.org/v1/payment-methods/PIxxxxxxxxxxxxxxxxxx/verify \
-H "Authorization: Bearer sk_live_your_api_key" \
-H "Content-Type: application/json" \
-d '{ "verification_code": "FNX7Q2M" }'/v1/payment-methodsList Payment Methods
Without customer_id, the payment methods the processor holds for the key's business. With customer_id, the instruments linked to that customer (enabled cards and bank accounts, plus bank accounts still awaiting verification).
Parameters
| Field | Type | Required | Description |
|---|---|---|---|
| customer_id | string (query) | optional | A customer id of this business. An unknown customer returns an empty list. |
Response
| Field | Type | Description |
|---|---|---|
| object | string | "list". |
| data[].id | string | Payment method id (source for /v1/charges). |
| data[].object | string | "payment_method". |
| data[].type | string | "card" or "bank_account". |
| data[].status | string | Bank accounts in the customer_id list only: requires_verification or verified. |
| data[].card | object | Cards: { brand, last4, exp_month, exp_year }. Absent for bank accounts. |
| data[].bank_account | object | Bank accounts: { last4, account_type }. Absent for cards. |
| data[].name | string | null | Cardholder or account holder. |
| data[].created | string | null | ISO 8601. |
| has_more | boolean | Always false (no pagination on this endpoint). |
curl -X GET "https://api.hlhunt.org/v1/payment-methods?customer_id=c1m9k2x7e0001qz8f4h2b6n3p" \
-H "Authorization: Bearer sk_live_your_api_key"/v1/customers/{customer_id}/payment-methodsList a Customer's Payment Methods
Lists the enabled instruments linked to a customer. This endpoint returns HL Hunt's internal instrument id as id and the chargeable processor-level id as processor_instrument_id (both are accepted as source on POST /v1/charges). Bank accounts still awaiting microdeposit verification are not listed here; GET /v1/payment-methods?customer_id=… includes them with their status.
Parameters
| Field | Type | Required | Description |
|---|---|---|---|
| customer_id | string (path) | required | The customer's id. 404 not_found when it belongs to another business. |
Response
| Field | Type | Description |
|---|---|---|
| object | string | "list". |
| data[].id | string | Internal instrument id (also accepted as source on /v1/charges). |
| data[].processor_instrument_id | string | null | The processor-level id — the same value GET /v1/payment-methods returns as id. |
| data[].type | string | "card" or "bank_account". |
| data[].card | object | Cards: { brand, last4, exp_month, exp_year }. Absent for bank accounts. |
| data[].bank_account | object | Bank accounts: { bank_name (the account holder name), last4, account_type }. Absent for cards. |
| data[].name / created | Holder name and ISO 8601 creation time. | |
| total / has_more | Count of rows; has_more is always false. |
curl -X GET https://api.hlhunt.org/v1/customers/c1m9k2x7e0001qz8f4h2b6n3p/payment-methods \
-H "Authorization: Bearer sk_live_your_api_key"Endpoints
/v1/capture-session/v1/payment-methods/v1/payment-methods/v1/payment-methods/{payment_method_id}/verify/v1/payment-methods/v1/customers/{customer_id}/payment-methodsCreate a card capture session (browser)
curl -X POST https://api.hlhunt.org/v1/capture-session \
-H "Authorization: Bearer pk_live_your_publishable_key" \
-H "Content-Type: application/json" \
-d '{ "ttl": "10m" }'Save a card (from a token)
curl -X POST https://api.hlhunt.org/v1/payment-methods \
-H "Authorization: Bearer sk_live_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"type": "card",
"payment_token": "9f1c3b…token from hlhunt.js…",
"token_reference": "ref_from_hlhunt_js",
"name": "Jenny Rosen",
"customer_id": "c1m9k2x7e0001qz8f4h2b6n3p",
"card": { "last4": "4242", "brand": "visa", "exp_month": 12, "exp_year": 2029 }
}'Save a bank account (ACH)
curl -X POST https://api.hlhunt.org/v1/payment-methods \
-H "Authorization: Bearer sk_live_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"type": "bank_account",
"name": "Jenny Rosen",
"customer_id": "c1m9k2x7e0001qz8f4h2b6n3p",
"bank_account": {
"routing_number": "021000021",
"account_number": "9876543210",
"account_type": "checking"
}
}'Verify a bank account (microdeposit code)
curl -X POST https://api.hlhunt.org/v1/payment-methods/PIxxxxxxxxxxxxxxxxxx/verify \
-H "Authorization: Bearer sk_live_your_api_key" \
-H "Content-Type: application/json" \
-d '{ "verification_code": "FNX7Q2M" }'List Payment Methods
curl -X GET "https://api.hlhunt.org/v1/payment-methods?customer_id=c1m9k2x7e0001qz8f4h2b6n3p" \
-H "Authorization: Bearer sk_live_your_api_key"List a Customer's Payment Methods
curl -X GET https://api.hlhunt.org/v1/customers/c1m9k2x7e0001qz8f4h2b6n3p/payment-methods \
-H "Authorization: Bearer sk_live_your_api_key"