Softlemon API Documentation
  • Merchant API
  • Partner API
  • Guides
API basics
    API ConventionsEnvironmentsError HandlingRate LimitsSCA and PSD2SupportChangelog
Integration flows
    Accept a Card PaymentAuthorize Now, Capture LaterRefund a PaymentCharge a Returning CustomerAccept an Alternative PaymentPaysafecard via Skrill: Customer JourneySet Up WebhooksIntegrate as a Partner
Reference
    Merchant Transaction WebhooksTransaction StatusesDuplicate Payment Protection
Integration flows

Accept a Card Payment

This guide walks through taking a one-time card payment from start to finish: verify the cardholder with 3D Secure, create the sale and confirm the result. It is the recommended starting point for a new integration.

Before you start

  • You need an API key, provisioned by your SoftLemon admin team and sent on every request as Authorization: Bearer {YOUR_API_KEY}.
  • All requests in this guide run against the sandbox at https://api.sandbox.softlemons.com.
  • You need a return URL on your site for the 3D Secure redirect (the auth_url below).
  • Amounts in requests are in major units, so 12.50 means EUR 12.50. Responses and webhooks report amounts in minor units, so the same value comes back as 1250.
  • Use the sandbox card numbers from the 3D Secure test cards section of the Merchant API reference to exercise each outcome.

Step 1: Verify the cardholder with 3D Secure

Start with POST /api/v1/3ds/verify because card payments must be authenticated before they are charged. SoftLemon runs a server-managed 3DS flow, so you never handle CAVV, ECI or DS Transaction IDs yourself. The gateway stores them and attaches them to the payment later.

curl https://api.sandbox.softlemons.com/api/v1/3ds/verify \ --request POST \ --header 'Content-Type: application/json' \ --header 'Authorization: Bearer {YOUR_API_KEY}' \ --data '{ "amount": 12.5, "currency": "EUR", "card": { "number": "4200000000000091", "exp_month": 12, "exp_year": 2030, "name": "John Doe", "cvv": "123" }, "auth_url": "https://yoursite.com/checkout/3ds-complete" }'

With the test card 4200000000000091 the issuer does not require a challenge and the response returns immediately:

JSON
{ "success": true, "message": "3DS authentication completed (frictionless)", "code": "", "data": { "id": 166, "public_id": "tds_01k2h4x9m3n5p7q9r1s3t5v7w9", "amount": 1250, "currency": "EUR", "version": "2.2.0", "eci": "05", "cavv": "AAABA0UREQAAAAAAAAAAAAAAAAA=", "status": "full_auth", "auth_type": "frictionless", "challenge_url": null } }

status: full_auth means authentication succeeded. Keep data.public_id (tds_01k2h4x9m3n5p7q9r1s3t5v7w9 here). It is the verification's identifier and you will pass it when creating the sale in Step 3. Treat it as an opaque string. The numeric id next to it is deprecated and will leave the response on a date announced in the changelog; see conventions.

The immediate response is not always a full authentication. Read status, not just the HTTP code:

statusMeaningWhat to do
full_authThe issuer authenticated the cardholder.Continue to Step 3.
attemptThe issuer could not fully authenticate the cardholder but returned an attempt proof (ECI 06 or 01).Continue to Step 3. The attempt proof is forwarded with the payment.
unavailableThe card is not enrolled for 3DS or authentication could not be performed. No authentication data exists and the message says so.You may still create the sale. It is processed without 3DS and can be soft-declined with ERR_3DS_REQUIRED where SCA is mandatory.
failedThe issuer refused the authentication. Returned as HTTP 400 with code ERR_3DS_FAILED.Do not create the sale. Ask for another card.

Step 2: Handle the challenge when the issuer requires one

Some cards trigger a bank challenge instead (test with 4200000000000042). In that case the response carries a challenge_url and no final status yet:

  1. Redirect the cardholder to challenge_url. The bank runs its own verification there (a code, an app approval or similar).
  2. When the cardholder finishes, the gateway redirects them back to your auth_url with query parameters: ?card_verification_id={public_id}&status={outcome}, where card_verification_id is the verification's tds_... public id and the outcome is one of the statuses in the table above (full_auth, attempt, unavailable or failed).
  3. On full_auth or attempt, use the card_verification_id value exactly like the public_id from the frictionless case, passed through unchanged. On unavailable, decide whether to charge without 3DS. On failed, show the customer a payment failure and let them try another card.

Your return page should handle every outcome. Nothing has been charged at this point in either flow, and a sale that references a failed or unfinished verification is refused with HTTP 422.

Step 3: Create the sale

Now charge the card with POST /api/v1/transactions. Three fields matter beyond the card details:

  • transaction_type: "sale" charges immediately with no separate capture step. (Use auth instead when you want to reserve funds first. See Authorize now, capture later.)
  • card_verification_data.id links the 3DS verification from Step 1 or 2: the public_id from the verify response or the card_verification_id from the redirect. The stored CAVV, ECI and DS Transaction ID are attached automatically.
  • reference is your own id for this payment. The gateway allows only one active payment per reference, which protects your customer from double charges. See the duplicate protection guide.
curl https://api.sandbox.softlemons.com/api/v1/transactions \ --request POST \ --header 'Content-Type: application/json' \ --header 'Authorization: Bearer {YOUR_API_KEY}' \ --data '{ "transaction_type": "sale", "amount": 12.5, "currency": "EUR", "reference": "ORDER-912346", "card": { "number": "4200000000000091", "exp_month": 12, "exp_year": 2030, "name": "John Doe", "cvv": "123" }, "card_verification_data": { "id": "tds_01k2h4x9m3n5p7q9r1s3t5v7w9" } }'

A successful response returns the transaction:

JSON
{ "success": true, "message": "Transaction initiated", "code": "", "data": { "id": 2, "related_trans_id": null, "merchant_id": 1, "amount": 1250, "currency": "EUR", "status": "success", "transaction_type": "sale", "merchant_trans_id": "ORDER-912346", "acquirer_trans_id": "514009741995", "acquirer_auth_code": "400066", "created_at": "2025-05-20T09:18:47.000000Z" } }

Store data.id. It is the gateway transaction id you will use for refunds, voids and status checks. See the transaction statuses guide for what each status value means.

Step 4: Confirm the result

Treat webhooks as your primary confirmation. When the sale completes, your registered endpoint receives a signed transaction.succeeded event within seconds. Verify the signature, acknowledge with a 2xx response and update your order. The webhook integration guide covers registration, verification and retries.

For an on-demand answer (for example after downtime or during reconciliation), poll the status endpoint:

curl https://api.sandbox.softlemons.com/api/v1/transactions/2/status \ --header 'Authorization: Bearer {YOUR_API_KEY}'

Errors you should handle

ResponseMeaningWhat to do
401Missing or invalid API key.Check the Authorization header.
409 with ERR_DUPLICATEThe reference already has an active payment. data.transaction_id is the original.Treat the payment as already made. Do not retry with the same reference.
422Validation failed. The response lists the field errors.Fix the request. Nothing was charged.
400 with ERR_DO_NOT_RETRYThe same declined card was retried within the cooldown window.Wait or ask the customer for another card.
400 with ERR_3DS_REQUIREDThe sale was sent without card_verification_data and the issuer requires authentication. The transaction is recorded as failed with status_reason 3ds_required; data.transaction is that row and data.next_action points at POST /api/v1/3ds/verify.Go back to Step 1, run the verification, then create a new sale with card_verification_data.id. The refused sale cannot be continued.
422 with ERR_3DS_REQUIRED or ERR_3DS_FAILEDThe card_verification_data.id you sent belongs to a verification that has not finished, or that ended in failed.Wait for the outcome, or run a new verification. Nothing was charged.
Last modified on September 7, 2026
ChangelogAuthorize Now, Capture Later
On this page
  • Before you start
  • Step 1: Verify the cardholder with 3D Secure
  • Step 2: Handle the challenge when the issuer requires one
  • Step 3: Create the sale
  • Step 4: Confirm the result
  • Errors you should handle
JSON
JSON