Skip to content
LIQFYdocs
PTEN
Go to dashboard

API Reference

Complete catalogue of every public Liqfy v1 endpoint. All paths are relative to the production base URL — https://liqfy.com.br/v1. (A dedicated sandbox host is not yet available.)

Authenticate every request with your API key in the apikey header — apikey: <LIQFY_API_KEY> (lq_test_*/lq_live_*). All bodies are JSON. All amounts are integers in the smallest currency unit.

The API is organised around a few resources:

  • Charges (/v1/charges) — create and read Pix charges: object: "charge", ch_… ids, header Idempotency-Key, pix block. This is where every integration starts.
  • Wallets (/v1/wallets) — your account balance.
  • Payment operations (/v1/payments) — refunds, cancellation, stats and other operations on a charge, addressed by its raw id (the ch_ prefix stripped).
  • Webhooks (/v1/webhooks) — register endpoints and inspect deliveries.

#Charges

/v1/charges.

Public contract from API conventions §3. Every response is built by an allowlist serializer — no internal id, PSP name, cost or raw provider payload can appear here.

#Create charge

POST /v1/charges — Pix-first shortcut: POST /v1/pix/charges fixes payment_method: "pix" in the body and returns the exact same Charge.

Headers

HeaderRequiredNotes
apikeyyesYour API key (lq_test_* / lq_live_*).
Idempotency-Keyyes8–128 chars. A financial write — missing it is a 400.

Body

FieldTypeRequiredDescription
amountintegeryesSmallest currency unit, > 0.
currencystringnoDefaults to "BRL". Pix charges must use BRL.
payment_methodstringyes"pix" — the only value accepted today.
descriptionstringnoUp to 500 chars; folded into metadata.description on read.
customer.namestringno
customer.documentstringnoCPF or CNPJ.
customer.emailstringno
customer.phonestringno
metadataobjectnoFree-form; reserved/underscore-prefixed keys are stripped.

Example

bash
curl -X POST https://liqfy.com.br/v1/pix/charges \
  -H "apikey: $LIQFY_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
    "amount": 1000,
    "currency": "BRL",
    "description": "Pedido #12345",
    "customer": { "name": "Maria Silva", "document": "12345678901" }
  }'

Response 201 Created

json
{
  "id": "ch_a1b2c3d4-0000-0000-0000-000000000009",
  "object": "charge",
  "amount": 1000,
  "currency": "BRL",
  "status": "pending",
  "payment_method": "pix",
  "customer": { "name": "Maria Silva", "document": "12345678901" },
  "pix": {
    "br_code": "000201BRCODEPIX",
    "qr_code_url": "https://qr.example/img.png",
    "expires_at": "2026-07-23T15:00:00.000Z"
  },
  "checkout_url": "https://checkout.liqfy.com.br/a1b2c3d4-0000-0000-0000-000000000009",
  "settlement": {},
  "metadata": { "order_id": "12345" },
  "created_at": "2026-07-23T14:30:00.000Z"
}

pix.txid (when the provider bound one) and settlement.end_to_end_id (only once paid and settled) are contextual Pix data — see PIX. charge.id is never equal to charge.pix.txid.

checkout_url is Liqfy's hosted checkout for the charge — redirect the payer there instead of rendering your own Pix screen. It comes back on both create and GET /v1/charges/{id}, and can't be derived from charge.id (the checkout path drops the ch_ prefix).

Public status vocabulary: pending, processing, paid, failed, expired, cancelled, refunded, disputed.


#Get charge

GET /v1/charges/{id}

Accepts either the ch_…-prefixed id or the raw internal id. Scoped to the caller's account — a charge from another account 404s.

Response 200 OK — same Charge shape as creation.


#List charges

Status: the query contract below (ListChargesDto + PaymentsService.listCharges) is implemented and unit-tested, but the GET /v1/charges route is not yet wired to an HTTP controller — calling it today 404s. Use the GET /v1/payments list until this ships. Tracked as a follow-up on the /v1/charges contract.

http
GET /v1/charges?limit=25&starting_after=ch_01J…&status=paid
ParamTypeDefaultDescription
limitinteger251–100.
starting_afterstringA charge id (ch_…) of the same resource — cursor, not an offset.
statusenumPublic status vocabulary (pending, paid, …).
created_afterISO 8601Inclusive lower bound on created_at.
created_beforeISO 8601Inclusive upper bound on created_at.
customer_idstringFilters by payer document.
json
{
  "object": "list",
  "data": [],
  "has_more": false,
  "next_cursor": null
}

page/offset are never accepted on this cursor list — only starting_after.


#Wallets

/v1/wallets. Your account's balances. Read-only, scoped to your API key.

#Get balance

GET /v1/wallets/balance

The currency query param is optional. Without it (the recommended form) the response carries every currency on the account in balances[], plus the primary currency's fields (BRL by default) at the top:

bash
curl "https://liqfy.com.br/v1/wallets/balance" \
  -H "apikey: $LIQFY_API_KEY"

Response 200 OK

json
{
  "available": 880,
  "pending": 0,
  "total": 880,
  "retained": 0,
  "currency": "BRL",
  "next_release_at": null,
  "next_release_amount": null,
  "balances": [
    { "currency": "BRL", "available": 880, "pending": 0, "total": 880 }
  ],
  "primary": { "currency": "BRL", "available": 880, "pending": 0, "total": 880 }
}
FieldDescription
availableSpendable/payoutable balance of the primary currency, smallest unit (centavos for BRL). 880 = R$ 8,80.
pendingSettled but still held — not spendable yet (primary currency).
totalavailable + pending of the primary currency.
retainedTotal under retention (PENDING holds) of the primary currency.
currencyPrimary currency (ISO 4217). Defaults to BRL.
next_release_atWhen the next pending tranche releases, or null when nothing is scheduled.
next_release_amountAmount of that next release (smallest currency unit), or null.
balances[]One entry per currency: { currency, available, pending, total }.
primaryThe primary currency (same fields as a balances[] entry).

The top-level fields (available/pending/total/currency) are the primary currency — kept for backward compatibility. For multi-currency accounts, iterate over balances[].

#Single currency (legacy shape)

GET /v1/wallets/balance?currency=BRL

Pass currency to get the flat single-currency shape:

bash
curl "https://liqfy.com.br/v1/wallets/balance?currency=BRL" \
  -H "apikey: $LIQFY_API_KEY"
json
{
  "available": 880,
  "pending": 0,
  "currency": "BRL",
  "next_release_at": null,
  "next_release_amount": null
}

Auth & errors. Always send the apikey header. A missing or invalid key → 401 (never 404). If you get a 404 on this route, the request never reached Liqfy — it's almost always a wrong path (/v1/wallets/balance, plural, with the /v1 prefix) or an intermediary proxy/gateway that doesn't forward /v1/wallets/*.

#List wallets

GET /v1/wallets

Every wallet on the account (operational, per-currency), same auth.

bash
curl https://liqfy.com.br/v1/wallets \
  -H "apikey: $LIQFY_API_KEY"

Response 200 OK

json
[
  {
    "currency": "BRL",
    "walletType": "OPERATIONAL",
    "balance": 880,
    "pendingBalance": 0
  }
]

balance/pendingBalance are in the smallest currency unit — the same amounts GET /v1/wallets/balance surfaces as available/pending.


#Payment operations

/v1/payments. These routes operate on the same charge you created via /v1/charges, addressed by its raw id (the ch_ prefix stripped). They cover operations the /v1/charges surface doesn't expose yet — refunds, cancellation, stats, receipts — plus create/read/list in the transaction wire format (tx_… ids, WAITING_PAYMENT/PAID statuses). To create a charge, prefer Charges.

#Create payment

POST /payments

Body

FieldTypeRequiredDescription
amountintegeryesSmallest currency unit, > 0.
currencystring (3–8)noDefaults to "BRL". Examples: BRL, EUR, USD, USDT.
paymentMethodsstring[]yesOne or more of: PIX, CREDIT_CARD, MBWAY, MULTIBANCO, BOLETO, CRYPTO.
customerIdstringnoYour internal user id, echoed in webhooks.
customerNamestringno
customerDocumentstringnoCPF, CNPJ, NIF, or other tax id.
customerEmailstringnoValidated as RFC 5322.
metadataobjectnoFree-form. Reserved key: returnUrl (used by CREDIT_CARD). For MBWAY you must include phone (E.164).
webhookUrlstringnoPer-transaction webhook URL override.
idempotencyKeystringyesUnique per intended request. Replays return the original.

Response 201 Created

json
{
  "id": "a1b2c3d4-e5f6-4789-9abc-def012345678",
  "status": "WAITING_PAYMENT",
  "amount": 24900,
  "currency": "BRL",
  "paymentMethods": ["PIX"],
  "customerName": "Maria Silva",
  "customerDocument": "12345678909",
  "customerEmail": "maria@example.com",
  "metadata": { "orderId": "ORD-7821" },
  "createdAt": "2026-04-25T15:42:11.000Z"
}

Method-specific fields (pixQrCode, cardRedirectUrl, etc.) are populated by GET /payments/{id} after Liqfy finalises the charge with the underlying acquirer (typically <3s).


#Get payment

GET /payments/{id}

Response 200 OK

json
{
  "id": "a1b2c3d4-e5f6-4789-9abc-def012345678",
  "status": "PAID",
  "amount": 24900,
  "currency": "BRL",
  "paymentMethods": ["PIX"],
  "paidWith": "PIX",
  "pixQrCode": "data:image/png;base64,iVBOR...",
  "pixCopyPaste": "00020126580014br.gov.bcb.pix...",
  "pixExpiresAt": "2026-04-25T16:12:11.000Z",
  "cardRedirectUrl": null,
  "mbEntity": null,
  "mbReference": null,
  "mbExpiresAt": null,
  "boletoBarcode": null,
  "boletoLine": null,
  "boletoPdfUrl": null,
  "boletoExpiresAt": null,
  "cryptoAddress": null,
  "cryptoAmount": null,
  "cryptoNetwork": null,
  "cryptoCurrency": null,
  "providerFee": 75,
  "platformFee": 200,
  "netAmount": 24625,
  "metadata": { "orderId": "ORD-7821" },
  "createdAt": "2026-04-25T15:42:11.000Z",
  "paidAt": "2026-04-25T15:43:08.000Z"
}

Fields are null when not applicable to the chosen paymentMethods.


#List payments

GET /payments

Query parameters

ParamTypeDefaultDescription
pageint1
limitint20Max 100.
statusenumSee Status enum below.
startDateISO 8601Inclusive lower bound on createdAt.
endDateISO 8601Inclusive upper bound on createdAt.
sortBystringcreatedAtAny top-level field.
sortOrderenumdescasc or desc.

Response 200 OK

json
{
  "data": [ { "id": "tx_...", "...": "..." } ],
  "total": 142,
  "page": 1,
  "limit": 20
}

#Stats

GET /payments/stats?days=7

Response 200 OK

json
{
  "totalTransactions": 142,
  "paidTransactions": 119,
  "todayTransactions": 8,
  "successRate": 84,
  "volumeByCurrency": [
    { "currency": "BRL", "volume": 1245000, "count": 95 },
    { "currency": "EUR", "volume": 89400,   "count": 24 }
  ],
  "todayVolumeByCurrency": { "BRL": 24900, "EUR": 8990 },
  "dailyVolume": [
    { "date": "2026-04-19", "currencies": { "BRL": 89000 } },
    { "date": "2026-04-20", "currencies": { "BRL": 124500, "EUR": 4500 } }
  ],
  "methodBreakdown": [
    { "method": "PIX",         "volume": 980000, "count": 78 },
    { "method": "CREDIT_CARD", "volume": 265000, "count": 34 },
    { "method": "MULTIBANCO",  "volume": 89400,  "count": 7 }
  ]
}

#Metrics

GET /payments/metrics?days=7&currency=BRL

Conversion-funnel and volume metrics for your merchant account. days defaults to 7; currency is optional (filters to a single currency). Scoped to your apikey.


#Refund payment

POST /payments/{id}/refund

Refunds a settled transaction (PAID or APPROVED), full or partial. Live in production. For PIX this maps to a BACEN devolução (by endToEndId) or a PIX-out cashout, per strategy.

Body

FieldTypeRequiredDescription
idempotencyKeystringyes8–128 chars. Replays return the original refund.
amountintegernoSmallest currency unit, > 0. Omit for a full refund. Must be ≤ remaining refundable.
reasonstringnoUp to 500 chars, stored for your records.
strategyenumnoPIX only: devolution or cashout. Defaults to cashout.
passFeeToTenantbooleannoCashout refunds: debit the PIX-out fee from your wallet. Default false. (field name reflects the current v1 wire format — a merchant-scoped alias ships with the public API migration; see the glossary.)
destinationKeystringnoCashout refunds: send to a specific PIX key instead of the payer's document.

Response 201 Created

json
{
  "id": "b2c3d4e5-f6a7-4890-9abc-def012345678",
  "transactionId": "a1b2c3d4-e5f6-4789-9abc-def012345678",
  "amount": 24900,
  "currency": "BRL",
  "status": "PENDING",
  "reason": "customer_request",
  "createdAt": "2026-04-25T15:50:00.000Z"
}

Refund status advances asynchronously (PENDINGIN_PROGRESSREFUNDED/FAILED) as the acquirer confirms — subscribe to the payment.refunded webhook. The transaction only moves to REFUNDED once the refunded total reaches the original amount; partial refunds leave it PAID/APPROVED.

Refund support is provider-dependent: PIX (BrasilCash) and card (Stripe) are live. Other providers return a REFUND_NOT_SUPPORTED error.


#List refunds

GET /payments/{id}/refunds

Returns every refund issued against a transaction (newest first).


#Cancel payment

POST /payments/{id}/cancel

Cancels an in-flight charge — valid only while WAITING_PAYMENT, PENDING, or PROCESSING. Settled (PAID/APPROVED) charges must be refunded, not cancelled. Returns the updated transaction and fires payment.failed.


#Resend webhook

POST /payments/{id}/resend-webhook

Re-emits the transaction's current status as a fresh webhook delivery — useful when your endpoint was down.

json
{ "resent": true, "status": "PAID" }

#Provider status

GET /payments/{id}/provider-status

Live status straight from the acquirer (bypasses our cache) — for debugging stuck charges.

json
{
  "localStatus": "WAITING_PAYMENT",
  "provider": "brasilcash",
  "providerTransactionId": "bc_...",
  "providerStatus": "PENDING",
  "rawResponse": { "...": "..." }
}

Returns "error": "TRANSACTION_HAS_NO_PROVIDER_REFERENCE" when the charge never reached a provider.


#Receipt

GET /payments/{id}/receipt

Provider receipt (PDF) for a settled transaction, where the acquirer exposes one (e.g. BrasilCash PIX).

json
{ "contentType": "application/pdf", "base64": "JVBERi0xLjcK..." }

#Webhooks

#Register endpoint

POST /webhooks/endpoints

json
{
  "url": "https://merchant.example.com/hooks/liqfy",
  "events": ["charge.paid", "charge.failed", "charge.expired"]
}

Event names (charge-related): charge.created, charge.paid, charge.failed, charge.expired, payout.created, payout.paid, payout.failed. These are delivered in the versioned evt_ envelope and signed X-Liqfy-Signature: t=<unix>,v1=<hex> — see Webhooks. Subscribe to these.

The endpoint also accepts an older event family (payment.created, payment.completed, payment.failed, payment.expired, payment.refunded, withdrawal.*, and the payment.status_changed/withdrawal.status_changed aliases), delivered with a { event, data } envelope and X-Liqfy-Signature: sha256=<hex>. New integrations don't need these — use the charge.*/payout.* names above. The full, current list is authoritative at GET /webhooks/event-catalog.

Response 201 Created

json
{
  "id": "e5f6a7b8-c9d0-4123-9ef0-123456789012",
  "url": "https://merchant.example.com/hooks/liqfy",
  "events": ["charge.paid", "charge.failed", "charge.expired"],
  "secret": "b8f3a9c1d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1",
  "status": "ACTIVE"
}

secret is shown once — store it immediately.


#List endpoints

GET /webhooks/endpoints

json
{
  "data": [
    {
      "id": "wh_...",
      "url": "https://...",
      "events": ["payment.status_changed"],
      "status": "ACTIVE",
      "createdAt": "...",
      "updatedAt": "..."
    }
  ]
}

secret is never returned by this endpoint.


#Rotate secret

POST /webhooks/endpoints/{id}/rotate-secret

json
{ "id": "e5f6a7b8-c9d0-4123-9ef0-123456789012" }

Response 200 OK

json
{ "id": "e5f6a7b8-c9d0-4123-9ef0-123456789012", "secret": "<64-char hex>" }

The new secret is shown once. Rotation is an instantaneous server-side swap — there is no overlap window. Every webhook signed after this call uses the new secret, so make your verifier accept both the old and new secret across your deploy, then drop the old one (see webhooks.md).


#Update endpoint

PATCH /webhooks/endpoints/{id}

Update URL, subscribed events, or status (ACTIVE/INACTIVE). Send only the fields to change.

json
{ "url": "https://merchant.example.com/hooks/v2", "status": "ACTIVE" }

Response 200 OK{ id, url, events, status }. Secret never returned.


#Delete endpoint

DELETE /webhooks/endpoints/{id}

Permanently removes the endpoint. All pending deliveries to it are abandoned.

Response 200 OK{ "deleted": true }.


#Event catalog

GET /webhooks/event-catalog

Returns the full, current list of subscribable public event names with descriptions (legacy: true on deprecated families). No auth required. Charge/payout-relevant excerpt:

json
[
  { "event": "charge.created", "description": "Cobrança criada (Pix/cartão gerado, aguardando pagamento)." },
  { "event": "charge.paid",    "description": "Cobrança paga e confirmada (Pix/cartão liquidado)." },
  { "event": "charge.failed",  "description": "Cobrança falhou ou foi cancelada." },
  { "event": "charge.expired", "description": "Cobrança expirou sem pagamento." },
  { "event": "payout.created", "description": "Saque solicitado." },
  { "event": "payout.paid",    "description": "Saque liquidado com sucesso." },
  { "event": "payout.failed",  "description": "Saque falhou ou foi rejeitado." },
  { "event": "payment.completed",    "description": "[legado] Cobrança paga — use charge.paid.", "legacy": true },
  { "event": "withdrawal.completed", "description": "[legado] Saque liquidado — use payout.paid.", "legacy": true }
]

#List deliveries

GET /webhooks/deliveries

ParamDescription
statusPENDING, PROCESSING, DELIVERED, FAILED, CANCELLED
eventTypeFilter by event name (payment.completed, …).
endpointIdFilter by registered endpoint.
limitDefault 25, max 100.
offsetDefault 0.
json
{
  "data": [
    {
      "id": "wd_...",
      "endpointId": "wh_...",
      "eventType": "payment.completed",
      "status": "DELIVERED",
      "attempts": 1,
      "maxAttempts": 15,
      "lastStatusCode": 200,
      "lastError": null,
      "nextRetryAt": null,
      "deliveredAt": "2026-04-25T15:43:09.000Z",
      "createdAt": "2026-04-25T15:43:08.000Z",
      "updatedAt": "2026-04-25T15:43:09.000Z",
      "payload": { "event": "payment.completed", "data": { "...": "..." } }
    }
  ],
  "total": 142,
  "limit": 25,
  "offset": 0
}

#Delivery stats

GET /webhooks/stats

json
{
  "total": 1402,
  "PENDING": 3,
  "PROCESSING": 1,
  "DELIVERED": 1380,
  "FAILED": 12,
  "CANCELLED": 6
}

#Status enum

The table below is the detailed status enum returned by the /v1/payments transaction routes (and eventType/payload.data.status on their webhook deliveries). POST/GET /v1/charges never emit these values; they emit the smaller public vocabulary (pending, processing, paid, failed, expired, cancelled, refunded, disputed — see Getting Started §5), which the enum below maps onto.

Transaction statusDescriptionPublic charge.status
PENDINGInternal — being created. Rarely surfaced.pending
WAITING_PAYMENTAwaiting customer action.pending
PROCESSINGCard / 3DS in flight.processing
PAIDSettled — non-card methods.paid
APPROVEDSettled — card methods.paid
REFUSEDAcquirer or issuer declined.failed
CANCELLEDCancelled before completion.cancelled
EXPIREDTime window elapsed.expired
REFUNDEDFully refunded.refunded
CHARGEBACKIssuer raised a chargeback (cards).disputed
DISPUTECardholder opened a dispute (cards).disputed

payment.completed fires for PAID and APPROVED; charge.paid fires for the same underlying transition. payment.failed fires for REFUSED, CANCELLED, EXPIRED, CHARGEBACK, DISPUTE; charge.failed and charge.expired split that older payment.* family by outcome.


#Webhook payload schema

#Canonical envelope (charge.* / payout.*)

json
{
  "id": "evt_5f3a9b2c1d4e5f6a7b8c9d0e1f2a3b4c",
  "object": "event",
  "api_version": "2026-07-23",
  "type": "charge.paid",
  "created_at": "2026-07-23T14:31:00.000Z",
  "data": {
    "object": {
      "id": "ch_a1b2c3d4-0000-0000-0000-000000000009",
      "object": "charge",
      "amount": 24900,
      "currency": "BRL",
      "status": "paid",
      "payment_method": "pix",
      "settlement": { "end_to_end_id": "E-END-TO-END-99" }
    }
  }
}
FieldAlways presentDescription
idyesevt_… — stable per business event, identical across retries. Dedup on this.
objectyesAlways "event".
api_versionyesContract version, e.g. 2026-07-23.
typeyescharge.created | charge.paid | charge.failed | charge.expired | payout.created | payout.paid | payout.failed.
data.objectyesThe same public Charge/Payout object the REST API returns — same serializer, same field names.

#payment.* / withdrawal.* envelope

json
{
  "event": "payment.completed",
  "data": {
    "transactionId": "tx_...",
    "amount": 24900,
    "status": "PAID",
    "previousStatus": "WAITING_PAYMENT",
    "paidWith": "PIX",
    "providerFee": 75,
    "platformFee": 200,
    "netAmount": 24625,
    "occurredAt": "2026-04-25T15:43:08.000Z"
  }
}
FieldAlways presentDescription
transactionIdyesMatches the id returned at creation.
amountyesSmallest currency unit.
statusyesCurrent status — see Status enum.
previousStatusyesStatus before this transition.
paidWithon PAID/APPROVEDThe actual method used.
providerFeeon PAID/APPROVEDAcquirer fee, smallest unit.
platformFeeon PAID/APPROVEDLiqfy fee, smallest unit.
netAmounton PAID/APPROVEDamount - providerFee - platformFee.
occurredAtyesISO 8601 UTC.

#Webhook headers

HeaderDescription
X-Liqfy-Signaturecharge.*/payout.* events: t=<unix>,v1=<hex> HMAC of "<t>.<rawBody>". payment.*/withdrawal.* events: sha256=<hex> HMAC of the raw body.
X-Liqfy-Delivery-IdUnique per delivery attempt — stable across retries of the same attempt; use for transport-level dedup.
X-Liqfy-Event-TypeMirrors type / event.

See webhooks.md for verification samples.

#HTTP status codes

CodeUsed for
200Successful read
201Resource created
204No content (e.g. logout)
400Validation error, or a financial write missing Idempotency-Key
401Missing / invalid apikey
403Blocked by the fraud / velocity guard (error code fraud.*)
404Resource not found
409Idempotency conflict — error.code: "idempotency_key_reused"
422Business-rule rejection passed through from provider configuration (e.g. no PSP configured)
429Rate limited
500Server error — safe to retry (idempotency protects)
503Upstream acquirer unavailable — retry with backoff

See Errors for the full canonical error envelope (error.type/error.code/error.message, request_id, X-Request-Id).