Server-to-Server Payments
Available via: API only (private key)
Overview
Server-to-server (S2S) payments let you collect card details on your own frontend and process the payment entirely through the API. This gives you full control over the checkout experience.
Important: S2S card payments require sending card data to
https://api-card.inflowpay.com(not the standard API URL). This endpoint handles PCI-compliant card tokenization.
Prerequisites
Server-to-server payments must be enabled on your account before you can use them. If S2S is not enabled, you will receive the error:
"Not authorized to create payment, server to server payment not enabled, please contact support"Contact [email protected] to request activation.
When to Use S2S
| Approach | Best For |
|---|---|
| Hosted Checkout (payment links) | Simple integration, no frontend work needed |
| SDK (iframe card form) | Custom checkout with PCI compliance handled for you |
| Server-to-Server | Full control, custom UI, PCI-compliant infrastructure |
If your server-to-server flow is for a marketplace, creator platform, or SaaS product with multiple sellers, use Connect alongside the payment API. Connect shows how to create sub-merchants and send merchant API calls with X-On-Behalf-Of.
Payment Flow
Step 1: Create the Payment
curl -X POST https://api-card.inflowpay.com/api/server/payment \
-H "X-Inflow-Api-Key: inflow_prod_your_key" \
-H "Content-Type: application/json" \
-d '{
"products": [
{ "name": "Pro Plan", "price": 4999, "quantity": 1 }
],
"currency": "EUR",
"customerEmail": "[email protected]",
"billingCountry": "FR",
"postalCode": "75001",
"purchasingAsBusiness": false,
"firstName": "John",
"lastName": "Doe",
"autoConfirm": true,
"pricingMode": "TAX_EXCLUSIVE",
"threeDsSuccessUrl": "https://yoursite.com/3ds-success",
"threeDsFailureUrl": "https://yoursite.com/3ds-failure",
"metadatas": {
"orderId": "order_12345"
}
}'Request Body
| Field | Type | Required | Description |
|---|---|---|---|
products | array | Yes | List of products (name, price in cents, quantity) |
currency | string | Yes | EUR or USD |
customerEmail | string | Yes | Customer's email address |
billingCountry | string | Yes | ISO 3166-1 alpha-2 country code (e.g., FR, DE, US) |
purchasingAsBusiness | boolean | Yes | Whether the customer is purchasing as a business |
postalCode | string | If US | Billing postal code (required for US customers) |
businessName | string | If business | Business name (required if purchasingAsBusiness is true) |
taxId | string | If business | Tax ID (required if purchasingAsBusiness is true) |
firstName | string | No | Customer first name (recommended for 3DS) |
lastName | string | No | Customer last name (recommended for 3DS) |
savePaymentMethod | boolean | No | Save the card for future use (default: false). See Saving and reusing payment methods below. |
useCustomerPaymentMethod | boolean | No | Use the customer's last saved payment method (default: false). See Saving and reusing payment methods below. |
autoConfirm | boolean | No | Auto-confirm a new-card payment if no 3DS is required, and complete the charge automatically after a successful 3DS challenge (default: false). Not needed for a frictionless saved-card charge, which is always confirmed during creation. |
captureMode | string | No | AUTOMATIC (default) or MANUAL — authorize now, capture later. See Manual Capture |
pricingMode | string | No | TAX_EXCLUSIVE (default) or TAX_INCLUSIVE |
statementDescriptor | string | No | Bank statement descriptor (max 11 characters) |
threeDsSuccessUrl | string | No | Redirect URL after successful 3DS |
threeDsFailureUrl | string | No | Redirect URL after failed 3DS |
metadatas | object | No | Custom metadata |
Response
{
"id": "pay_abc123",
"amountInCents": 4999,
"currency": "EUR",
"status": "INITIATION",
"customerEmail": "[email protected]",
"threeDsSessionUrl": null
}Step 2: Handle 3D Secure (If Required)
If threeDsSessionUrl is not null, the customer's bank requires 3DS authentication:
- Redirect the customer to the
threeDsSessionUrl. - The customer completes authentication with their bank.
- The customer is redirected to your
threeDsSuccessUrlorthreeDsFailureUrl.
Step 3: Confirm the Payment (Only When Required)
Confirmation is needed only when the payment object shows depositStatus: "requires_confirmation" and no 3DS redirect is pending. Always read depositStatus on the create response (or on GET /api/payment/{paymentId}) before calling confirm:
| Situation | depositStatus after creation | What to do |
|---|---|---|
New card, autoConfirm: false (default), no 3DS | requires_confirmation | Call confirm |
New card, autoConfirm: true, no 3DS | succeeded / requires_capture | Nothing — already confirmed |
New card, 3DS required (threeDsSessionUrl set) | requires_confirmation | Redirect to 3DS; after success, confirm automatically if autoConfirm: true, otherwise call confirm |
Saved card (useCustomerPaymentMethod: true), no 3DS | succeeded / requires_capture | Nothing — the card is charged during creation, whatever autoConfirm is |
| Saved card, 3DS required | requires_confirmation | Redirect to 3DS; then same rule as a new card |
curl -X POST https://api.inflowpay.com/api/server/payment/{paymentId}/confirm \
-H "X-Inflow-Api-Key: inflow_prod_your_key"Do not call confirm on a payment that is already confirmed. It is not needed, and it adds a concurrent write on the payment at the very moment the card processor's notification is being processed. Confirm is not a "make sure it went through" call — use webhooks or
GET /api/payment/{paymentId}for that.
SDK users: If you use the Inflow SDK (iframe card form), the SDK handles confirmation automatically — you do not need to call this endpoint.
Step 4: Verify Payment Status
curl https://api.inflowpay.com/api/payment/{paymentId} \
-H "X-Inflow-Api-Key: inflow_prod_your_key"Or set up a webhook to receive automatic notifications when the payment status changes.
Saving and Reusing Payment Methods
You can save a customer's card for future purchases and reuse it without asking them to enter their card details again.
This mechanism is the building block for one-click upsells, downsells, and instant purchases — see One-Click Upsells, Downsells & Instant Purchases for the full funnel guide.
How it works
savePaymentMethod: true— When the customer pays with a card, that card is saved and linked to their email (customerEmail). Use this on the first payment when you want to allow one-click or faster checkout later.useCustomerPaymentMethod: true— For a returning customer, use their last saved card instead of collecting new card details. The customer must use the samecustomerEmailas when the card was saved.
You cannot use both parameters in the same request: either you save a new card, or you use an existing one.
Typical flow
- First purchase — Customer enters their card. Send
savePaymentMethod: trueto store it. - Later purchases — Customer returns (same email). Send
useCustomerPaymentMethod: trueand omit card details. The saved card is charged during the create call itself: the response comes back withdepositStatus: "succeeded"and the payment reachesCHECKOUT_SUCCESSas soon as the processor's notification arrives. Do not call confirm — it is only needed if the bank requested 3DS (threeDsSessionUrlset) andautoConfirmis false. See One-Click Upsells for the full flow.
Important: last saved card is used
When useCustomerPaymentMethod: true is set, the system charges the last saved card for that customer. If a customer saves multiple cards over time, the most recently saved card becomes the active one.
Selecting a specific card among multiple saved payment methods is not currently supported but is planned. Contact [email protected] for updates.
Customer identification
The saved payment method is tied to customerEmail. For reuse to work, the customer must use the same email address across payments. If they have no saved card yet, useCustomerPaymentMethod: true will fail.
Charge limit: 5 attempts per customer per 30 days
A saved card may be charged at most 5 times per rolling 30 days for a given customer with your account, successful and declined attempts included. Beyond that, useCustomerPaymentMethod: true is refused with a 400 carrying the error code SAVED_PAYMENT_METHOD_ATTEMPT_LIMIT_EXCEEDED, nothing is charged, and the card has to be collected again (SDK card form or Checkout). This mirrors the card networks' rules on stored credentials: repeated charges or retries on the same card, even when they succeed, expose the merchant to excessive-retry fees and network blocks. Never retry a declined saved-card charge in a loop. Subscription payments are not blocked by this limit. See Charge Limit Reached for the response body and the fallback to apply.
Update a Payment Before Confirmation
You can update a server payment before it's finalized:
curl -X POST https://api.inflowpay.com/api/server/payment/{paymentId} \
-H "X-Inflow-Api-Key: inflow_prod_your_key" \
-H "Content-Type: application/json" \
-d '{
"products": [
{ "name": "Updated Product", "price": 5999, "quantity": 1 }
]
}'Error Handling
If the payment fails, check the lastDepositAttempt field in the payment object:
{
"lastDepositAttempt": {
"status": "failed",
"amount": 4999,
"paymentMethod": "CARD",
"error": "card_declined",
"attemptedAt": "2025-01-15T10:31:00.000Z"
}
}A 400 on payment creation with message.errorCode set to SAVED_PAYMENT_METHOD_ATTEMPT_LIMIT_EXCEEDED means the customer's saved card has reached its charge limit: collect the card again instead of retrying.
See Error Handling for a complete list of error codes.
Minimum Payment Amounts
Each currency has a minimum payment amount. Payments below these thresholds will be rejected:
| Currency | Minimum Amount |
|---|---|
| EUR | €1.50 (150 cents) |
| USD | $2.00 (200 cents) |
Tips
- Always provide
firstNameandlastName— this significantly improves 3DS authentication success rates. - Use
autoConfirm: truefor a smoother flow when 3DS is not required. - Set up webhooks instead of polling for payment status updates.
- Tax is calculated automatically based on
billingCountry.
Updated about 1 month ago
