One-Time Payments

Take a single card payment with your own form: tokenize the card in the browser with hlhunt.js, then charge the token from your server. If you would rather not build a form, use a Checkout Session.

How It Works

  1. The browser sends the card to HL Hunt's vault through hlhunt.js and gets a token. The number never reaches your server.
  2. Your server charges the token once with POST /v1/charges, or saves it with POST /v1/payment-methods and charges the saved id.
  3. You read the status from the response and finish the order on the charge.succeeded webhook.

Raw card numbers are never accepted by the API: a request carrying card.number is rejected with 400 pci_vault_required. (An older version of this page showed type: "PAYMENT_CARD" with the number in the body; that shape has never been accepted by the current API.)

Step 1: Tokenize the card in the browser

Load hlhunt.js with your publishable key and call createToken:

<script src="https://payments.hlhunt.org/js/hlhunt.js"></script>
<script>
  var hlhunt = HLHunt('pk_live_your_publishable_key');

  hlhunt.createToken({
    card: { number: '4242 4242 4242 4242', exp_month: 12, exp_year: 2029, cvc: '123', name: 'Jenny Rosen' }
  }).then(function (result) {
    if (result.error) { showError(result.error.message); return; }
    // result.token, result.reference, result.card = { last4, exp_month, exp_year, brand }
    postToYourServer({ token: result.token, reference: result.reference, card: result.card });
  });
</script>

The promise resolves with token, reference and a card display object, or with an error. Use createBankToken for a bank account.

Step 2: Charge the token, or save it

From your server, with your secret key. To charge once:

POST /v1/charges
Authorization: Bearer sk_live_your_secret_key
Idempotency-Key: order-1234-attempt-1

{
  "amount": 5000,
  "currency": "USD",
  "source": "<result.token>",
  "vault_reference": "<result.reference>",
  "description": "Order #1234",
  "customer_email": "jenny@example.com",
  "metadata": { "order_id": "1234" }
}

To keep the card for later charges, save it first (optionally on a customer created with POST /v1/customers; customer ids are 25-character strings starting with c):

POST /v1/payment-methods
Authorization: Bearer sk_live_your_secret_key

{
  "type": "card",
  "payment_token": "<result.token>",
  "token_reference": "<result.reference>",
  "card": { "last4": "4242", "brand": "visa", "exp_month": 12, "exp_year": 2029 },
  "name": "Jenny Rosen",
  "customer_id": "c1m9k2x7e0001qz8f4h2b6n3p"
}

→ 201 { "id": "pcivault_9f1c…", "object": "payment_method", "type": "card", ... }
   use "id" as "source" on POST /v1/charges — now or months later

Amounts are in cents (5000 = $50.00). Send an Idempotency-Key so a retried request cannot charge twice.

Step 3: Read the result

A card charge is answered synchronously:

201 Created
{
  "id": "TRgk3Nq7vZ2cXb8wY1mLpR4d",
  "object": "charge",
  "amount": 5000,
  "currency": "USD",
  "status": "succeeded",
  "description": "Order #1234",
  "source": "<result.token>",
  "created": "2026-10-27T16:00:00.000Z",
  "metadata": { "order_id": "1234" }
}

A decline is not a charge object but an error:

400 Bad Request
{
  "error": {
    "type": "card_error",
    "code": "charge_failed",
    "message": "Your card was declined. Please try a different card or contact your bank."
  }
}
statusMeaning
succeededThe card was charged. Store the id; it is what POST /v1/refunds takes as charge.
pendingAn ACH debit (bank account) that has not cleared yet. Wait for charge.succeeded or poll GET /v1/charges/{id}.
failedSeen on GET /v1/charges/{id} and charge.failed, with failure_code and failure_message.
canceledFully refunded or voided.

Next Steps