Skip to content
Last updated

Invoices

Invoices let you request crypto payments to a specific address for a specific token amount. Vilna tracks incoming transactions in real time, detects partial payments, overpayments, and confirms payments once they reach the required block depth. Each invoice has a public URL you can share with payers - no authentication required on their end. To see how invoices fit into a broader payment flow, check the payment monitoring use case.

Non-custodial

Vilna watches the blockchain and tells you when an invoice is paid. It never holds the funds or controls the receiving wallet: the address is yours - one you provide, or derive from your own extended public key - and the payer sends to it directly. See the Non-custodial Service Notice.

Before you begin

You need a Vilna API key with api:invoice:read and api:invoice:write permissions. See Authentication for details.

Invoice lifecycle

An invoice moves through the following statuses:

StatusDescription
pendingWaiting for payment. No transactions received yet.
underpaidPartial payment received. The amount is below the requested total.
paidFull amount received. Waiting for block confirmations.
confirmedPayment confirmed with sufficient block confirmations.
overpaidReceived more than the requested amount.
cancelledCancelled by the merchant.
expiredThe invoice passed its deadline without receiving full payment.

Status transitions:

FromToTrigger
pendingpaidFull payment received
pendingunderpaidPartial payment received
pendingoverpaidMore than required amount received
pendingcancelledCancelled by merchant
pendingexpiredDeadline passed
underpaidpaidRemaining amount received
underpaidoverpaidMore than remaining amount received
underpaidcancelledCancelled by merchant
underpaidexpiredDeadline passed
paidconfirmedBlock confirmations reached
overpaidconfirmedBlock confirmations reached

confirmed, cancelled, and expired are terminal - no further transitions.

Create a crypto payment invoice

To create an invoice, specify the deposit address, token (as a CAIP-19 asset identifier), amount in base units, and an expiration time. The address must belong to your project and must not already have an active invoice.

curl -X POST "https://api.vilna.io/v1/invoices" \
  -H "X-Api-Key: ${VILNA_API_KEY}" \
  -H "Content-Type: application/json" \
  -d '{
    "address": "0x742d35Cc6634C0532925a3b844Bc9e7595f2bD18",
    "asset_gid": "eip155:56/bep20:0x55d398326f99059fF775485246999027B3197955",
    "amount": "42500000",
    "expires_at": "2026-06-15T18:00:00Z",
    "metadata": {
      "order_id": "ORD-20260615-1042",
      "customer": "john@example.com"
    }
  }'

Response (abbreviated):

{
  "item": {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "address": "0x742d35Cc6634C0532925a3b844Bc9e7595f2bD18",
    "asset_gid": "eip155:56/bep20:0x55d398326f99059fF775485246999027B3197955",
    "amount": "42500000",
    "received_amount": "0",
    "status": "pending",
    "expires_at": "2026-06-15T18:00:00Z",
    "metadata": {
      "order_id": "ORD-20260615-1042",
      "customer": "john@example.com"
    },
    "created_at": "2026-06-15T10:30:00Z",
    "updated_at": "2026-06-15T10:30:00Z",
    "status_logs": [
      {
        "status": "pending",
        "comment": null,
        "changed_at": "2026-06-15T10:30:00Z"
      }
    ],
    "transactions": []
  },
  "references": {
    "tokens": {},
    "blockchains": {}
  }
}

Key fields:

  • amount - the requested payment in base units (smallest token denomination). For USDT on BSC with 6 decimals, "42500000" equals 42.5 USDT.
  • metadata - optional key-value pairs for linking invoices to your internal records.
  • expires_at - the payment deadline. After this time, the invoice moves to expired if full payment has not arrived.
One invoice per address

An address can have only one active invoice at a time. Attempting to create a second invoice for the same address returns a 409 Conflict error.

Share the payment page

Every invoice has a public endpoint that requires no authentication. The invoice UUID acts as the access token - anyone with the link can view the payment details.

curl "https://api.vilna.io/v1/public/invoices/550e8400-e29b-41d4-a716-446655440000"

The public response includes the address, amount, status, and transaction history - everything a payer needs. It excludes merchant-only fields like metadata.

Use this endpoint to build custom payment pages, or direct payers to your hosted payment UI.

Check invoice status

Retrieve full invoice details, including the status log and detected transactions.

curl "https://api.vilna.io/v1/invoices/550e8400-e29b-41d4-a716-446655440000" \
  -H "X-Api-Key: ${VILNA_API_KEY}"

Once a payment arrives, the response includes transaction details:

{
  "item": {
    "status": "paid",
    "received_amount": "42500000",
    "transactions": [
      {
        "hash": "0xabc123def456789012345678901234567890123456789012345678901234abcd",
        "block_number": 12345678,
        "is_success": true,
        "detected_at": "2026-06-15T12:05:00Z",
        "confirmed_at": null,
        "deposits": [
          {
            "asset_gid": "eip155:56/bep20:0x55d398326f99059fF775485246999027B3197955",
            "amount": "42500000",
            "is_matched": true
          }
        ]
      }
    ],
    "status_logs": [
      { "status": "pending", "comment": null, "changed_at": "2026-06-15T10:30:00Z" },
      { "status": "paid", "comment": null, "changed_at": "2026-06-15T12:05:00Z" }
    ]
  }
}

Each transaction includes:

  • is_success - whether the transaction succeeded on-chain. Failed transactions appear in the list but do not count toward payment.
  • confirmed_at - null until the transaction reaches the required number of block confirmations.
  • deposits - individual token movements within the transaction. The is_matched flag indicates whether the token matches the one the invoice expects.

Handle partial payments and overpayments

Vilna tracks every deposit and updates the invoice status automatically:

  • Partial payment: The invoice moves to underpaid. The received_amount field reflects the running total. Additional payments accumulate until the full amount is reached.
  • Full payment: The invoice moves to paid, then to confirmed once block confirmations are sufficient.
  • Overpayment: If the total received exceeds the requested amount, the status moves to overpaid. Handle refunds in your application logic.

You can monitor these transitions by polling the invoice endpoint or by setting up webhook notifications for invoice status changes.

List invoices

Retrieve all invoices with optional status filtering and pagination.

# List all pending and underpaid invoices
curl "https://api.vilna.io/v1/invoices?status=pending&status=underpaid&limit=20&page=1" \
  -H "X-Api-Key: ${VILNA_API_KEY}"

The list endpoint returns InvoiceSummary objects (without status_logs and transactions). Use GET /invoices/{invoice_id} for full details on a specific invoice.

Cancel an invoice

Cancel an active invoice to free the address for new invoices. Only invoices in pending or underpaid status can be cancelled. You can include an optional comment (up to 64 characters) explaining the reason.

curl -X POST "https://api.vilna.io/v1/invoices/550e8400-e29b-41d4-a716-446655440000/actions/cancel" \
  -H "X-Api-Key: ${VILNA_API_KEY}" \
  -H "Content-Type: application/json" \
  -d '{
    "comment": "Customer requested cancellation"
  }'

A successful cancellation returns 204 No Content. The operation is idempotent - cancelling an already-cancelled invoice returns the same response.

Request body is optional

The comment field is optional. You can send an empty body or omit the request body entirely.

Next steps