Skip to content
LIQFYdocs
PTEN
Go to dashboard

Card Payments

Accept Visa, Mastercard, American Express, Elo, Hipercard, and other major schemes through a Liqfy-hosted payment page — no card data ever touches your servers, 3D Secure handled automatically.

#Flow at a glance

text
1. POST /v1/payments  ──────▶  Liqfy returns transactionId + status WAITING_PAYMENT
2. GET  /v1/payments/{id} ───▶  Within ~1s, cardRedirectUrl is populated
3. Redirect (or open in iframe) the customer to cardRedirectUrl
4. Customer enters card details + completes 3DS challenge on Liqfy's secure page
5. Customer is bounced back to your returnUrl (success or failure)
6. Webhook payment.completed (or payment.failed) confirms the outcome — trust the webhook, not the redirect

Why a redirect? It keeps cardholder data off your infrastructure entirely. Your application — backend or frontend — never sees a PAN, CVV, or expiry. Liqfy's processor handles 3DS, tokenisation, and fraud screening on our side.

#1. Create the payment

Endpoint POST /v1/payments

bash
curl -X POST https://liqfy.com.br/v1/payments \
  -H "apikey: $LIQFY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "amount": 8990,
    "currency": "EUR",
    "paymentMethods": ["CREDIT_CARD"],
    "customerName": "João Costa",
    "customerEmail": "joao@example.com",
    "metadata": {
      "orderId": "ORD-9821",
      "returnUrl": "https://shop.example.com/orders/9821/return"
    },
    "idempotencyKey": "ORD-9821"
  }'
FieldNotes
amountSmallest unit (cents). 8990 = € 89,90.
currencyBRL, EUR, or USD (additional currencies on request).
paymentMethods["CREDIT_CARD"]
metadata.returnUrlWhere to send the customer after they finish (success or failure).
customerEmailStrongly recommended — used for receipts, fraud signals, and chargeback dispute proof.

Response 201 Created

json
{
  "id": "b2c3d4e5-f6a7-4890-9bcd-ef0123456789",
  "status": "WAITING_PAYMENT",
  "amount": 8990,
  "currency": "EUR",
  "paymentMethods": ["CREDIT_CARD"],
  "createdAt": "2026-04-25T15:50:11.000Z"
}

#2. Redirect the customer

Fetch the transaction once cardRedirectUrl is populated (typically <1s):

bash
curl https://liqfy.com.br/v1/payments/b2c3d4e5-f6a7-4890-9bcd-ef0123456789 \
  -H "apikey: $LIQFY_API_KEY"
json
{
  "id": "b2c3d4e5-f6a7-4890-9bcd-ef0123456789",
  "status": "WAITING_PAYMENT",
  "amount": 8990,
  "currency": "EUR",
  "cardRedirectUrl": "https://checkout.liqfy.com.br/c/b2c3d4e5-f6a7-4890-9bcd-ef0123456789",
  "createdAt": "2026-04-25T15:50:11.000Z"
}

Send the customer to cardRedirectUrl:

html
<a href="{{ cardRedirectUrl }}">Pay with card</a>

Or do a server-side 302 Found:

js
res.redirect(303, transaction.cardRedirectUrl);

After the customer completes the flow (or aborts), they bounce back to the metadata.returnUrl you supplied — with ?transactionId=...&status=... appended. Do not trust those query parameters. They're informational only — wait for the webhook before fulfilling.

#3. Confirm via webhook

json
{
  "event": "payment.completed",
  "data": {
    "transactionId": "b2c3d4e5-f6a7-4890-9bcd-ef0123456789",
    "amount": 8990,
    "status": "APPROVED",
    "previousStatus": "PROCESSING",
    "paidWith": "CREDIT_CARD",
    "providerFee": 280,
    "platformFee": 90,
    "netAmount": 8620,
    "occurredAt": "2026-04-25T15:52:34.000Z"
  }
}

For card, the success status is APPROVED (not PAID). Both events arrive as payment.completed so a single handler covers both.

Card declines arrive as payment.failed with data.statusREFUSED, EXPIRED, CANCELLED. Common decline reasons (when surfaced by the issuer) are echoed in data.declineReason.

#Lifecycle

text
WAITING_PAYMENT  ──▶  PROCESSING  ──▶  APPROVED    ✓ fulfill order
                                  ──▶  REFUSED     issuer declined
                                  ──▶  CANCELLED   customer abandoned
                                  ──▶  EXPIRED     redirect link aged out (24h)

After settlement (D+1 to D+30 depending on scheme and your contract), you may also see:

text
APPROVED  ──▶  CHARGEBACK   issuer raised a chargeback
APPROVED  ──▶  DISPUTE      cardholder opened a dispute
APPROVED  ──▶  REFUNDED     you (or Liqfy) issued a refund

Each one fires a webhook so you can sync the order state.

#Test cards

In sandbox, the redirect page accepts these deterministic numbers (any future expiry, any CVV):

NumberOutcome
4111 1111 1111 1111Always APPROVED
4000 0000 0000 0002Always REFUSED — generic decline
4000 0000 0000 0069Always REFUSED — expired card
4000 0000 0000 9995Always REFUSED — insufficient funds
4000 0000 0000 3220Triggers 3DS challenge then APPROVED

See sandbox for the full list.

#FAQ

Q: Can I keep the customer on my domain? A: Phase 2 we'll ship an embeddable widget (iframe + tokeniser) that keeps your branding while still keeping cardholder data off your infrastructure. For now: hosted redirect.

Q: Do you support saved cards? A: Yes, but it requires your side to hold its own card-data-security compliance attestation. Email integrations@liqfy.com.br to enable the Vault API.

Q: Which 3DS version is used? A: 3DS 2.x with frictionless flow when the issuer permits. Strong Customer Authentication (SCA) is mandatory for EUR — this is enforced server-side.

Q: Refunds? A: Supported — POST /v1/payments/{id}/refund, full or partial. Card refunds settle through Stripe. See the API reference.

Q: My customer was charged but I never got a webhook. A: Check GET /v1/webhooks/deliveries — likely we tried and your endpoint returned a non-2xx. We retry up to 10 times. After that, contact support to replay.

#Next

MB WAY · Multibanco · API Reference