Enter at least two characters to search. Searching documentation… No matching sections. Try an endpoint, field or event name.

A charge is how you ask a customer for a payment, and the first thing to reach for. Creating one locks the exchange rate for 30 minutes, allocates a deposit address for exactly that payment and returns a checkout_url the customer can open. Pass whatever you already know about the customer and the order. It only needs the coin up front, so ask which one first. You do not need a payment link to charge someone.

On this page

New here? See how to charge a customer, step by step

The charge object

Field Description
id string Public code, also the last path segment of the checkout URL.
object "charge" The kind of object.
status "pending" | "underpaid" | "paid" | "expired" | "canceled" Pending until a confirmed payment arrives, the timer runs out or the charge is canceled. See how the status changes below.
payment_link string|null The link it was created through, if any.
fiat_amount, fiat_currency string What the customer owes, in your currency.
coupon, discount_amount string|null The coupon code the customer used and what it took off the price, or null without a coupon. fiat_amount is already the lower price.
asset string The coin and network the customer pays with: BTC, LTC, DOGE, BCH, ETH, SOL, BNB, TRX, USDT, USDC, USDT_BSC, USDC_BSC.
asset_amount string Exact crypto amount to send, rounded up at the locked rate.
exchange_rate string Fiat per one unit of the asset at creation.
received_amount, remaining_amount string|null, string What has been credited so far and what is still missing.
overpaid boolean True when the surplus exceeds your overpayment threshold.
refunded_amount, refunds string, array What you recorded as sent back: amount, txid, reason, explorer_url, created_at per refund.
deposit_address string The address this charge is paid into, made for it alone.
transactions array On-chain transfers matched to the charge: txid, amount, confirmations, required_confirmations, credited, explorer_url, seen_at. An unconfirmed transfer the chain stops reporting for 30 minutes (dropped or replaced) is removed again. explorer_url is null for a simulated test payment.
customer_email string|null Receives a receipt when the charge is paid, a notice when a payment falls short, and a notice of each refund you record.
customer_name string|null From customer.name, from the checkout when the link asks for it, or from the invoice.
customer object Everything known about who pays: email, name, ip_address, user_agent, reference, phone, country and metadata. See customer details below.
custom_fields array Labelled details: the customer's answers to the link's extra questions, or what you passed when creating the charge. Each has label and value.
invoice string|null The invoice this charge is an attempt to pay, if any.
timeline array What happened so far, oldest first: event, time. Left out when the charge comes embedded in an event, and in MCP server results.
description, metadata string|null, object Yours to fill: order numbers, references. The description is up to 500 characters. Metadata values must be scalar: 20 keys max, keys up to 40 characters, values up to 500.
success_url, cancel_url, success_message string|null What you set on the charge: the page the customer is offered a button back to once they have paid, the page they can go back to without paying, and what they are told once they have paid. Null ones fall back to the payment link's and then to your account's defaults, without showing it here.
checkout_url string The hosted payment page.
receipt_url string|null A printable receipt for the customer, once the charge is paid.
expires_at, paid_at, created_at string, string|null, string ISO 8601 timestamps in UTC.

Endpoints

  • GET /v1/charges List charges, newest first. Filter with ?status=, ?payment_link=, ?asset=, ?search= (the charge id, customer email or customer name), ?created_from= and ?created_to=.
  • POST /v1/charges Create a charge; the body is described below. Answers 201 with the charge, 429 with Retry-After once the account has created 300 charges in this mode in the last 30 minutes (canceled ones included, paid live ones not, through the API, MCP and the dashboard; payments customers start at checkout do not count), or 503 while no exchange rate is available.
  • GET /v1/charges/{id} Retrieve a charge and its current status.
  • POST /v1/charges/{id}/cancel Cancel a pending charge nothing has been sent to yet. Answers 409 once it is paid, underpaid, expired or canceled, and as soon as a transfer to it has been seen, even an unconfirmed one.
  • POST /v1/charges/{id}/send Email the customer the payment page of a pending charge. They have until expires_at to pay, so for a bill to pay later create an invoice instead. 409 unless it is pending and has a customer email; 429 with Retry-After within 10 minutes of the last email, or past 100 emails an hour for the account (shared with invoices and refund notices, and with the other accounts its owner owns). In test mode, only members of your account are emailed; anyone else answers 422 under errors.customer_email.
  • POST /v1/charges/{id}/accept Accept an underpaid charge, or a late payment on an expired or canceled one, as paid for what actually arrived. It is forwarded to you and charge.paid goes out. 409 for any other charge.
  • POST /v1/charges/{id}/refunds Record a refund you sent from your wallet. Body: amount (in the asset, up to 18 decimals), txid (optional, letters and digits, up to 100) and reason (optional, up to 255). Answers 201 with the updated charge; 422 when the amount is more than what was counted towards the charge, less what was already refunded; a late payment counts once it is accepted.
  • POST /v1/charges/{id}/simulate Test mode only: pay the charge without coins. Body: amount (optional, in the asset; left out, whatever is still due, so pass less to try an underpayment). 409 for a live charge, and for one that is not awaiting payment.

Creating a charge

Field Description
amount string What to charge, with up to two decimals, 0.01 or more. Required, except through a fixed-price payment link, which sets it.
currency string USD, EUR, GBP, CAD, AUD, CHF, TRY. Required, except through a payment link, whose currency is used instead.
asset string Required. A coin GET /v1/assets lists as accepted and, through a payment link, one of the coins the link accepts. The amount has to reach the coin's minimum.
payment_link string|null Only when the payment belongs to an existing link: the id of a payment link of the same mode to charge through. On an open-amount link, amount has to fall within its min_amount and max_amount; an archived link, or one past its expires_at or max_payments, answers 422.
customer object|null What you know about who pays; see customer details below.
customer_email string|null The older form of customer.email; when both are sent, customer.email is used.
coupon string|null The code of one of your coupons of this mode, taken off the amount. Answers 422 under errors.coupon when it cannot be used; see coupons.
description, metadata string|null, object|null As on the charge object.
custom_fields array|null Up to five labelled details shown with the charge in the dashboard: label (up to 60 characters) and value (up to 255). Use it to pass the answers to a payment link's questions.
success_url, cancel_url string|null https:// addresses on a public website, up to 2,048 characters: a button back once the customer has paid, and a link back from the checkout. They take precedence over the payment link's and your account's defaults.
success_message string|null Up to 1,000 characters, shown to the customer once they have paid and included in the receipt email. It takes precedence over the payment link's and your account's default.
send_email boolean Email the customer the payment page once the charge exists. Needs a customer email, and follows the limits of POST /v1/charges/{id}/send, refusing before anything is created.

A charge created through a payment link skips the questions its hosted checkout would ask: require_email, collect_name and required custom_fields are not enforced. Pass the answers you have under customer and custom_fields.

Example: create a charge

POST /v1/charges

Copy failed. Select the code and copy it manually.

curl -X POST 'https://api.velirapay.com/v1/charges' \
  --fail-with-body --silent --show-error --max-time 30 \
  -H "Authorization: Bearer ${VELIRAPAY_API_KEY:?Set VELIRAPAY_API_KEY}" \
  -H 'Accept: application/json' \
  -H 'Idempotency-Key: 4d6c1e1a-…' \
  -H 'Content-Type: application/json' \
  -d '{
    "amount": "150.00",
    "currency": "USD",
    "asset": "BTC",
    "customer": {
      "email": "buyer@example.com",
      "ip_address": "203.0.113.7",
      "user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 14_6) …",
      "reference": "cus_1042",
      "country": "FR"
    },
    "description": "Order #1042",
    "metadata": {"order_id": "1042"},
    "success_url": "https://example.com/orders/1042"
  }'
Response: 201 Created

Copy failed. Select the code and copy it manually.

{
  "data": {
    "id": "2t4bcuqkpkoy",
    "object": "charge",
    "status": "pending",
    "fiat_amount": "150.00",
    "fiat_currency": "USD",
    "asset": "BTC",
    "asset_amount": "0.0025",
    "received_amount": null,
    "remaining_amount": "0.0025",
    "overpaid": false,
    "exchange_rate": "60000",
    "deposit_address": "bc1q…",
    "transactions": [],
    "checkout_url": "https://velirapay.com/c/2t4bcuqkpkoy",
    "expires_at": "2026-09-17T00:30:00.000000Z",
    …
  }
}

Poll GET /v1/charges/{id} if you must, but a webhook endpoint tells you when a payment is seen on-chain and when a charge is paid, underpaid, expired or canceled, without polling.

Customer details

The customer object gathers what is known about who pays, to help you spot fraud before you ship anything. When the customer starts the payment on the hosted checkout, the IP address and browser (user agent) they did it from are recorded for you. When you create the charge from your own server, that request comes from you rather than from the customer, so pass what you know under customer instead:

Field Description
email, name string Who pays, up to 255 characters each. The receipt goes to the email; the older top-level customer_email still works.
ip_address string The IPv4 or IPv6 address your customer ordered from.
user_agent string Their browser's user agent, up to 1000 characters.
reference string Your own id for the customer, up to 255 characters.
phone string A phone number, as you have it, up to 50 characters.
country string The ISO 3166 two-letter country code, such as FR. Stored in capitals.
metadata object Anything else worth keeping, such as the age of their account or how many orders they placed: up to 20 keys with scalar values, under the same length limits as the charge's own metadata.

Any other key in customer is refused with 422, so a typo does not go unnoticed. The object comes back on the charge, in the dashboard and in every webhook about it, with null for whatever is not known and an empty metadata object.

How the status changes

  • pending → paid, underpaid Confirmed funds arrive, within or short of your underpayment tolerance.
  • pending → expired The rate lock runs out. Expiry waits while a transfer to the charge is still confirming, and for up to 3 hours while its chain cannot be read.
  • pending → canceled You cancel it, the customer switches to another coin on the checkout, or the invoice it belongs to is voided.
  • underpaid → paid The rest arrives. An underpaid charge never expires.
  • underpaid, expired, canceled → paid You accept an underpaid charge, or a late payment on an expired or canceled one, as paid, with POST /v1/charges/{id}/accept or in the dashboard (admins only).

How payments are detected

Every open charge's deposit address is checked about once a minute. A transfer shows up under transactions as soon as the network reports it, the checkout page tells the customer it was seen, and a charge.payment_detected webhook goes out with the transfer under data.transaction. It is credited once it has the required confirmations (2 for Bitcoin, 6 on Ethereum by default, other networks have their own); only then does the charge become paid or underpaid, with a charge.paid or charge.underpaid webhook. A charge does not expire while a transfer is still confirming.

Because the check runs once a minute, a transfer can already have confirmations when it is first seen, and on a fast chain charge.paid can follow charge.payment_detected within the same minute. A simulated test payment is recorded as confirmed straight away, without a charge.payment_detected.

Every charge is given a deposit address of its own, so a payment is matched to its charge by where it was sent rather than by its amount.

A payment that arrives after a charge expired or was canceled is still recorded against it. Its address is watched for 3 hours after expires_at, and every day at 04:00 UTC the addresses of closed charges up to 30 days old are read again, so a later payment can still surface the next morning. The charge keeps its status, a charge.late_payment webhook goes out, the dashboard flags the late payment, and you decide whether to accept it as paid.

A second transfer to a charge that is already paid raises charge.late_payment as well. It is counted automatically once confirmed, so received_amount, and possibly overpaid, change without a further webhook.

Refunds

A refund is a transfer you make yourself, from your own wallet: a payment that has been forwarded to you cannot be pulled back. Record it on the charge, in the dashboard or with POST /v1/charges/{id}/refunds, and the charge shows what was sent back, the customer is emailed if the charge has their email (at most once every 10 minutes for one charge, and counted against the account's 100 customer emails an hour), and a charge.refunded event goes out. A late payment is accepted before it is refunded, so that it is forwarded to you first.

Underpayments and overpayments

Customers rarely send the exact amount: wallets and exchanges take fees out of it. Each account sets an underpayment tolerance under Payment settings. A payment that lands within the tolerance settles the charge as paid; one that falls further short moves it to underpaid, where received_amount and remaining_amount tell you where it stands. The customer can top it up on the same checkout page until the rate lock ends, and you can accept it as paid from the dashboard. An underpaid charge never expires: it stays underpaid until the rest arrives or you accept it.

Paying too much never blocks a charge. When the surplus exceeds your overpayment threshold, overpaid is true so you can refund the difference.

A charge can only be created for an asset your account has a payout address for, and for at least the asset's minimum; anything else fails validation with 422 under errors.asset. See the coins and their minimums