Workflows

Credentials

A merchant integration is provisioned with the credentials below. MID, API Key, and API Secret are always required; Tokenization Key and Site ID are optional depending on your integration. All credentials are environment-specific — sandbox and production values differ and must be swapped at cutover (see §6).

CredentialTypeRequiredDescription
MIDGUIDRequiredFlexFactor Merchant Identification Number. Identifies the merchant account on every API call.
API KeyStringRequiredPublic client key (the AppKey) sent with the API Secret to /oauth2/token to obtain a Bearer token.
API SecretStringRequiredClient secret (the AppSecret) paired with the API Key to authenticate. Store securely server-side; never expose it to the client.
Tokenization KeyStringOptionalUsed only by the /tokenize API — passed as the environment query parameter to vault card data. Not sent on any other call.
Site IDGUIDOptionalIdentifies a specific site/descriptor under the MID. Used when a merchant has multiple descriptors or brands on one MID.

Example credential set

{
  "mid": "3f8a1c42-9b07-4e5d-bc6a-1d2e3f4a5b6c",
  "apiKey": "Kp9XmR2vQ7sL4wN8tB6yH3jF5dG1aZ0cVxU4eW7n",
  "apiSecret": "AQAAAAEAACcQwL8xR2vQ7sN4wB6yH3jF5dG1aZ0cVxU4eW7nKp9XmR2vQ7sL4wN8tB6yH3jF5dG1aZ0cV==",
  "tokenizationKey": "9Hq4Lm2Rv7Ns8Wb6Yh3Jf5Dg1Kp",
  "siteId": "a1b2c3d4-e5f6-4789-9abc-def012345678"
}

These are illustrative values — replace every credential with the ones issued for your account, and use the matching set for each environment.


1. Authentication

Every API call requires a valid Bearer token. Tokens have a 10-minute TTL — cache and reuse them; do not request a new token per call.

%%{init: {'theme': 'base', 'themeVariables': {'actorBkg': '#3C3C42', 'actorTextColor': '#FFFFFF', 'actorBorder': '#56565E', 'noteBkgColor': '#F8F8F8', 'noteTextColor': '#3C3C42', 'activationBkgColor': '#FF5B5B', 'activationBorderColor': '#E7575B', 'signalColor': '#56565E', 'signalTextColor': '#3C3C42'}}}%%
sequenceDiagram
    participant C as Integrator
    participant FF as FlexFactor API

    C->>FF: POST /v1/oauth2/token<br/>{AppKey, AppSecret}
    FF-->>C: {accessToken, expires}
    Note over C: Cache token — reuse until 401

    C->>FF: Any API call<br/>Authorization: Bearer {accessToken}
    alt 401 Unauthorized
        FF-->>C: 401
        C->>FF: POST /v1/oauth2/token<br/>(re-authenticate with AppKey + AppSecret)
        FF-->>C: {new accessToken}
        C->>FF: Retry original request once
        FF-->>C: Response
    else Success
        FF-->>C: 200 / 201 response
    end

Environments

EnvironmentBase URL
Sandboxhttps://api-sandbox.flexfactor.io/v1/
Productionhttps://api.flexfactor.io/v1/

2. Primary flows

2.1 CIT purchase ("sale")

The default flow for customer-initiated transactions. Synchronous — response is immediate. No special account enablement required.

%%{init: {'theme': 'base', 'themeVariables': {'actorBkg': '#3C3C42', 'actorTextColor': '#FFFFFF', 'actorBorder': '#56565E', 'noteBkgColor': '#F8F8F8', 'noteTextColor': '#3C3C42', 'activationBkgColor': '#FF5B5B', 'activationBorderColor': '#E7575B', 'signalColor': '#56565E', 'signalTextColor': '#3C3C42'}}}%%
sequenceDiagram
    participant C as Integrator
    participant FF as FlexFactor API

    Note over C: Original decline occurs at primary processor
    C->>FF: POST /v1/evaluate
    Note over C,FF: isMIT: false, transactionType: "PURCHASE"

    alt APPROVED
        FF-->>C: {result: "SUCCESS", status: "APPROVED", orderId: "ff-order-guid"}
        C->>C: Update order as approved — rescue succeeded
    else DECLINED
        FF-->>C: {result: "SUCCESS", status: "DECLINED"}
        C->>C: Return original decline to merchant
    else CHALLENGE
        FF-->>C: {result: "SUCCESS", status: "CHALLENGE"}
        C->>C: Treat as decline (gateway/orchestrator integrations)
    end

Minimal request (required fields)

POST /v1/evaluate
Authorization: Bearer {accessToken}
Content-Type: application/json

{
  "mid": "your-flexfactor-mid-guid",
  "isDeclined": true,
  "orderId": "your-ext-order-id",
  "siteId": "your-site-guid",
  "idempotencyKey": "550e8400-e29b-41d4-a716-446655440000",
  "isMIT": false,
  "transactionType": "PURCHASE",
  "customerIp": "203.0.113.45",
  "transaction": {
    "id": "txn-guid",
    "amount": 9900,
    "currency": "USD",
    "timestampUtc": "2026-05-22T14:30:00Z"
  },
  "payer": {
    "email": "[email protected]"
  },
  "billingInformation": {
    "firstName": "Jane",
    "lastName": "Smith",
    "addressLine1": "123 Main St",
    "city": "Austin",
    "state": "TX",
    "zipCode": "78701",
    "country": "United States",
    "countryCode": "US"
  },
  "paymentMethod": {
    "cardNumber": "tok_sandbox_abc123",
    "token": true,
    "cardBinNumber": "424242",
    "cardLast4Digits": "4242",
    "expirationMonth": 12,
    "expirationYear": 2028,
    "verificationValue": "123",
    "holderName": "Jane Smith",
    "cardType": "credit",
    "cardBrand": "Visa",
    "cardCountry": "US"
  },
  "merchant": {
    "id": "merchant-guid",
    "mcc": "5999",
    "country": "US"
  }
}

Field optimization ranks

FieldRankNote
paymentMethod.verificationValueCriticalDirectly drives rescue performance — include whenever available; absence has measurable negative impact
customerIpCritical (CIT)CIT only — directly drives rescue performance; omit for MIT (cardholder not present). Must be the end-user client IP, not a server or proxy IP
paymentMethod.cardBrand, cardType, cardCountryRequiredRequired fields; each contributes meaningful rescue signal
paymentMethod.holderNameRequiredRequired per field reference
transaction.responseCode, responseCodeSource, processorNameHighSignificantly improve rescue performance; not required for launch
deviceDetails (all child fields)HighSignificantly improve rescue performance when device context is available at decline time; not required for launch

High-value optional fields

{
  "transaction": {
    "responseCode": "05",
    "responseDescription": "Do Not Honor",
    "responseCodeSource": "processor",
    "processorName": "Stripe",
    "avsResultCode": "Y",
    "cvvResultCode": "M"
  },
  "payer": {
    "phone": "5125550123"
  },
  "deviceDetails": {
    "deviceType": "desktop",
    "deviceName": "Chrome 124",
    "deviceOS": "macOS 14",
    "browser": "Chrome",
    "userAgent": "Mozilla/5.0 ..."
  }
}

When deviceDetails is included, all five child fields (deviceType, deviceName, deviceOS, browser, userAgent) are required.

Response examples

// APPROVED
{"result": "SUCCESS", "status": "APPROVED", "orderId": "ff-3a7b2c1d-..."}

// DECLINED
{"result": "SUCCESS", "status": "DECLINED"}

// CHALLENGE (treat as decline in gateway integrations)
{"result": "SUCCESS", "status": "CHALLENGE"}

result reflects whether the API request succeeded (SUCCESS/FAILED). status reflects the transaction outcome. Always key your integration logic on status.

transaction.amount is in cents (9900 = $99.00). amountToRefund (for refunds) is in dollars.


2.2 CIT authorize + capture

Auth-only on evaluate, explicit capture step required. Requires FlexFactor to enable the authorize capability on the integrator account — not available by default.

%%{init: {'theme': 'base', 'themeVariables': {'actorBkg': '#3C3C42', 'actorTextColor': '#FFFFFF', 'actorBorder': '#56565E', 'noteBkgColor': '#F8F8F8', 'noteTextColor': '#3C3C42', 'activationBkgColor': '#FF5B5B', 'activationBorderColor': '#E7575B', 'signalColor': '#56565E', 'signalTextColor': '#3C3C42'}}}%%
sequenceDiagram
    participant C as Integrator
    participant FF as FlexFactor API

    Note over C: Original decline occurs at primary processor
    C->>FF: POST /v1/evaluate
    Note over C,FF: isMIT: false, transactionType: "AUTHORIZATION"

    alt CAPTUREREQUIRED
        FF-->>C: {result: "SUCCESS", status: "CAPTUREREQUIRED", orderId: "ff-order-guid"}
        C->>FF: POST /v1/capture
        Note over C,FF: {orderId: "ff-order-guid", amount: 9900,<br/>currency: "USD", idempotencyKey: "new-guid"}
        FF-->>C: {result: "SUCCESS", status: "APPROVED"}
        C->>C: Update order as approved
    else DECLINED
        FF-->>C: {result: "SUCCESS", status: "DECLINED"}
        C->>C: Return original decline to merchant
    end

Evaluate request — same as CIT Purchase with one change:

{
  "transactionType": "AUTHORIZATION"
}

Capture request

POST /v1/capture
Authorization: Bearer {accessToken}
Content-Type: application/json

{
  "orderId": "ff-3a7b2c1d-...",
  "amount": 9900,
  "currency": "USD",
  "idempotencyKey": "661f9511-f30c-42e5-b827-557766551111"
}

Default capture timeout is 5 minutes but is adjustable. orderId in the capture request must be the FlexFactor-returned GUID, not your external orderId.


2.3 MIT async purchase

Core MIT use case — recurring billing, unscheduled card on file (COF), or any merchant-initiated retry where the cardholder is not present. AsynchronousPOST /v1/evaluate returns SUBMITTED immediately; the rescue resolves via webhooks.

%%{init: {'theme': 'base', 'themeVariables': {'actorBkg': '#3C3C42', 'actorTextColor': '#FFFFFF', 'actorBorder': '#56565E', 'noteBkgColor': '#F8F8F8', 'noteTextColor': '#3C3C42', 'activationBkgColor': '#FF5B5B', 'activationBorderColor': '#E7575B', 'signalColor': '#56565E', 'signalTextColor': '#3C3C42'}}}%%
sequenceDiagram
    participant C as Integrator
    participant FF as FlexFactor API

    Note over C: Original decline occurs at primary processor
    C->>FF: POST /v1/evaluate
    Note over C,FF: isMIT: true, isRecurring: true, expiryDateUtc set

    FF-->>C: {result: "SUCCESS", status: "SUBMITTED", orderId: "ff-order-guid"}
    Note over C: Store orderId — resolution is async

    FF--)C: webhook: order.completed (rescue approved)
    FF--)C: webhook: order.cancelled (rescue declined)
    FF--)C: webhook: order.expired (not rescued before expiryDateUtc)

    Note over C,FF: Polling fallback if webhook missed
    C->>FF: GET /v1/orders/{orderId}
    FF-->>C: Current order status

Minimal request (MIT-specific required fields)

POST /v1/evaluate
Authorization: Bearer {accessToken}
Content-Type: application/json

{
  "mid": "your-flexfactor-mid-guid",
  "isDeclined": true,
  "orderId": "your-ext-order-id",
  "siteId": "your-site-guid",
  "idempotencyKey": "550e8400-e29b-41d4-a716-446655440000",
  "isMIT": true,
  "isRecurring": true,
  "expiryDateUtc": "2026-06-16T00:00:00Z",
  "transactionType": "PURCHASE",
  "transaction": {
    "id": "txn-guid",
    "amount": 9900,
    "currency": "USD"
  },
  "payer": { "email": "[email protected]" },
  "billingInformation": {
    "firstName": "Jane", "lastName": "Smith",
    "addressLine1": "123 Main St", "city": "Austin",
    "state": "TX", "zipCode": "78701",
    "country": "United States", "countryCode": "US"
  },
  "paymentMethod": {
    "cardNumber": "tok_sandbox_abc123", "token": true,
    "cardBinNumber": "424242", "cardLast4Digits": "4242",
    "expirationMonth": 12, "expirationYear": 2028,
    "holderName": "Jane Smith",
    "cardType": "credit", "cardBrand": "Visa", "cardCountry": "US"
  },
  "merchant": { "id": "merchant-guid", "mcc": "5999", "country": "US" }
}

expiryDateUtc is the deadline by which FlexFactor must complete the rescue — recommended up to 21 days. After this date the order moves to EXPIRED.

customerIp is not sent for MIT — the cardholder is not present and there is no client IP.

verificationValue (CVV) is generally not available for MIT. Omit it — sending a stale or empty CVV degrades rescue performance.

High-value MIT field — subscription object

{
  "subscription": {
    "subscriptionId": "sub-guid-or-merchant-id",
    "price": 9900,
    "currency": "USD",
    "interval": "monthly",
    "schemeTransactionId": "network-txn-id-from-original-cit"
  }
}

subscription.schemeTransactionId (the network transaction ID from the original CIT that established the credential) is the highest-impact field for rescue performance — include whenever available. The full subscription object is strongly recommended for all MIT flows.

Response

{"result": "SUCCESS", "status": "SUBMITTED", "orderId": "ff-3a7b2c1d-..."}

Webhook resolution

WebhookMeaningAction
order.completedRescue approved — funds capturedMark order as paid
order.cancelledRescue declined — no further attemptsReturn to dunning or write off
order.expiredexpiryDateUtc passed without resolutionTreat as failed

2.4 MIT async authorize

Auth-only MIT variant — requires the authorize capability enabled on the FlexFactor account (not default). Resolves asynchronously via order.capturerequired webhook; explicit /capture call required.

%%{init: {'theme': 'base', 'themeVariables': {'actorBkg': '#3C3C42', 'actorTextColor': '#FFFFFF', 'actorBorder': '#56565E', 'noteBkgColor': '#F8F8F8', 'noteTextColor': '#3C3C42', 'activationBkgColor': '#FF5B5B', 'activationBorderColor': '#E7575B', 'signalColor': '#56565E', 'signalTextColor': '#3C3C42'}}}%%
sequenceDiagram
    participant C as Integrator
    participant FF as FlexFactor API

    C->>FF: POST /v1/evaluate
    Note over C,FF: isMIT: true, isRecurring: true,<br/>transactionType: "AUTHORIZATION", expiryDateUtc set
    FF-->>C: {result: "SUCCESS", status: "SUBMITTED", orderId: "ff-order-guid"}

    FF--)C: webhook: order.capturerequired (auth approved)
    C->>FF: POST /v1/capture
    Note over C,FF: {orderId, amount, currency, idempotencyKey}
    FF-->>C: {result: "SUCCESS", status: "APPROVED"}

Evaluate request — same as MIT async purchase with one change:

{
  "transactionType": "AUTHORIZATION"
}

Capture request — identical to the capture call in section 2.2.


2.5 Sync MIT

Synchronous MIT variant — response is immediate (like CIT). No expiryDateUtc. Used when the integration requires a real-time result rather than async webhook resolution.

%%{init: {'theme': 'base', 'themeVariables': {'actorBkg': '#3C3C42', 'actorTextColor': '#FFFFFF', 'actorBorder': '#56565E', 'noteBkgColor': '#F8F8F8', 'noteTextColor': '#3C3C42', 'activationBkgColor': '#FF5B5B', 'activationBorderColor': '#E7575B', 'signalColor': '#56565E', 'signalTextColor': '#3C3C42'}}}%%
sequenceDiagram
    participant C as Integrator
    participant FF as FlexFactor API

    C->>FF: POST /v1/evaluate
    Note over C,FF: isMIT: true, isRecurring: true<br/>(no expiryDateUtc)

    alt APPROVED
        FF-->>C: {result: "SUCCESS", status: "APPROVED", orderId: "ff-order-guid"}
        C->>C: Update order as approved
    else DECLINED
        FF-->>C: {result: "SUCCESS", status: "DECLINED"}
        C->>C: Return original decline
    else SUBMITTED
        FF-->>C: {result: "SUCCESS", status: "SUBMITTED"}
        Note over C: Treat as async — await webhook
    end

Key differences from async MIT

Async MITSync MIT
expiryDateUtcRequiredOmit
ResponseAlways SUBMITTEDAPPROVED / DECLINED / SUBMITTED
ResolutionWebhooksSynchronous (may still fall back to webhook)
transactionType effectRespectedAuth/capture always, regardless of transactionType
Retry transaction.idFlexibleMust be identical across retries for matching

2.6 Cancel

Cancel (void) a rescue order that has not yet been captured. Behaves like a VOID — it stops an in-flight rescue rather than reversing a settled charge. Applicable to:

  • MIT orders in Draft, Processing, or CAPTUREREQUIRED state
  • CIT orders in CAPTUREREQUIRED state

To reverse a charge that has already been captured/settled, use the Refund flow (§2.7) instead.

Two endpoints are available depending on whether the FlexFactor orderId has been received:

EndpointID usedWhen to use
POST /v1/orders/{orderId}/cancelFlexFactor order GUIDPrimary — standard cancellation once orderId is known
POST /v1/orders/{external-order-id}/external-cancelMerchant's own order IDTimeout fallback — use when a cancel must be issued before the FlexFactor orderId has been received (e.g., evaluate response timed out)
%%{init: {'theme': 'base', 'themeVariables': {'actorBkg': '#3C3C42', 'actorTextColor': '#FFFFFF', 'actorBorder': '#56565E', 'noteBkgColor': '#F8F8F8', 'noteTextColor': '#3C3C42', 'activationBkgColor': '#FF5B5B', 'activationBorderColor': '#E7575B', 'signalColor': '#56565E', 'signalTextColor': '#3C3C42'}}}%%
sequenceDiagram
    participant C as Integrator
    participant FF as FlexFactor API

    alt FlexFactor orderId known (standard)
        C->>FF: POST /v1/orders/{orderId}/cancel
        Note over C,FF: Body: {} (empty)
        FF-->>C: 200 OK — {}
    else Timeout — orderId not yet received
        C->>FF: POST /v1/orders/{external-order-id}/external-cancel
        Note over C,FF: Empty request body
        FF-->>C: 200 OK
    end
    C->>C: Update order as cancelled

Option A — cancel by FlexFactor order ID (primary)

POST /v1/orders/{orderId}/cancel
Authorization: Bearer {accessToken}
Content-Type: application/json

{}
HTTP 200 OK
{}

Option B — external-cancel (timeout fallback)

Use when a cancel must be issued before the FlexFactor orderId has been received — identified by the merchant's own external order ID.

POST /v1/orders/{external-order-id}/external-cancel
Authorization: Bearer {accessToken}

{}
HTTP 200 OK

2.7 Refund

Refund a rescued transaction. FlexFactor refunds the cardholder directly (as MoR) and adjusts merchant settlement accordingly.

%%{init: {'theme': 'base', 'themeVariables': {'actorBkg': '#3C3C42', 'actorTextColor': '#FFFFFF', 'actorBorder': '#56565E', 'noteBkgColor': '#F8F8F8', 'noteTextColor': '#3C3C42', 'activationBkgColor': '#FF5B5B', 'activationBorderColor': '#E7575B', 'signalColor': '#56565E', 'signalTextColor': '#3C3C42'}}}%%
sequenceDiagram
    participant C as Integrator
    participant FF as FlexFactor API
    participant CH as Cardholder (bank)

    C->>FF: POST /v1/orders/{orderId}/refund
    Note over C,FF: {amountToRefund: 99.00, refundMessage: "..."}
    FF-->>C: 200 OK
    FF-->>CH: Refund issued to card
    FF--)C: webhook: order.refunded (async)
    C->>C: Update order as refunded

Request

POST /v1/orders/{orderId}/refund
Authorization: Bearer {accessToken}
Content-Type: application/json

{
  "amountToRefund": 99.00,
  "refundMessage": "Customer requested refund"
}

amountToRefund is in dollars, not cents. Partial refunds are supported. Double-refunds are blocked at the platform level.

refundMessage is optional.


2.8 Order inquiry

Retrieve the current status of a rescue order by its FlexFactor-assigned orderId.

%%{init: {'theme': 'base', 'themeVariables': {'actorBkg': '#3C3C42', 'actorTextColor': '#FFFFFF', 'actorBorder': '#56565E', 'noteBkgColor': '#F8F8F8', 'noteTextColor': '#3C3C42', 'activationBkgColor': '#FF5B5B', 'activationBorderColor': '#E7575B', 'signalColor': '#56565E', 'signalTextColor': '#3C3C42'}}}%%
sequenceDiagram
    participant C as Integrator
    participant FF as FlexFactor API

    C->>FF: GET /v1/orders/{orderId}
    FF-->>C: Order record with current status

    note over C,FF: Also available: GET /v1/orders<br/>with search filters

Request

GET /v1/orders/{orderId}
Authorization: Bearer {accessToken}

3. Order inquiry & timeout handling

3.1 What happens when the orderId is not returned due to a timeout

If a POST /v1/evaluate call times out before the response arrives, the integrator has not received the FlexFactor-assigned orderId. The recovery path:

%%{init: {'theme': 'base', 'themeVariables': {'actorBkg': '#3C3C42', 'actorTextColor': '#FFFFFF', 'actorBorder': '#56565E', 'noteBkgColor': '#F8F8F8', 'noteTextColor': '#3C3C42', 'activationBkgColor': '#FF5B5B', 'activationBorderColor': '#E7575B', 'signalColor': '#56565E', 'signalTextColor': '#3C3C42'}}}%%
sequenceDiagram
    participant C as Integrator
    participant FF as FlexFactor API

    C->>FF: POST /v1/evaluate (idempotencyKey: "abc-guid-123")
    Note over C,FF: Network timeout — no response received

    C->>C: Record: idempotencyKey = "abc-guid-123"<br/>and your external orderId

    alt Recovery: retry with the same idempotencyKey
        C->>FF: POST /v1/evaluate (idempotencyKey: "abc-guid-123")
        FF-->>C: Identical response to original<br/>(no duplicate order created)
        C->>C: Process response normally — capture orderId
    end

    alt Recovery: search by external orderId
        C->>FF: GET /v1/orders (filter by external orderId)
        FF-->>C: Matching order record with status
        C->>C: Determine final state from order status
    end

    alt Ongoing monitoring: poll by FlexFactor orderId (once known)
        C->>FF: GET /v1/orders/{ff-orderId}
        FF-->>C: Current order status
    end

Key points

ScenarioBehavior
Retry with same idempotencyKeyFlexFactor returns the identical response — no duplicate order is created
New idempotencyKey on retryA new order evaluation is created — use only if you are certain the first was never processed
GET /v1/orders/{id}Authoritative status lookup by FlexFactor orderId — use once orderId is known
GET /v1/orders with filtersSearch by your external orderId to find orders when the FF orderId was never received

The idempotencyKey is the primary safety net for retries. Always persist it before making the API call so it survives a crash or timeout.

3.2 Retry patterns

HTTP CodeRecommended action
200 / 201Process response normally — no retry needed
401Refresh access token; retry the request once
500 / 502 / 504Retry with exponential backoff — max 3 attempts
503Do not retry — fall back to original decline
400Do not retry — fix the request payload
Timeout / no responseRetry with the same idempotencyKey

3.3 Determining final transaction status after a timeout or uncertain state

Use GET /v1/orders/{id} as the authoritative source of truth for any order after a timeout or uncertain state.

What GET /v1/orders/{id} returns

The response carries a numeric status and a matching statusName:

statusstatusNameMeaningAction
0DraftMIT order received, not yet processingPending — await webhook / keep polling
1OnholdOrder temporarily on holdPending — keep polling
2CancelledOrder was cancelled (voided)Treat as not rescued
3ProblemA processing problem occurredTreat as not rescued; investigate
4ProcessingRescue in progressPending — await webhook / keep polling
5CapturerequiredAuth succeeded; capture requiredCall /capture to complete
6ReturnedOrder was refunded/returnedTreat as refunded
7CompletedRescue succeeded — funds capturedMark as paid

An MIT order that exceeds its expiryDateUtc resolves to Cancelled (status 2); the order.expired webhook still fires so you can distinguish expiry from a merchant/system cancellation.

The /evaluate response status (APPROVED, DECLINED, CHALLENGE, SUBMITTED, CAPTUREREQUIRED) is a separate field from the order statusName above — GET /orders does not return those words.

For CIT flows, the final state is always synchronous (APPROVED, DECLINED, or CHALLENGE) — a timeout on a CIT evaluate call means the response was lost in transit; retry with the same idempotencyKey to recover the deterministic result.


4. Tokenization confirmation

4.1 Tokenization is not required for PCI-scoped integrators

Integrators that are PCI-DSS certified and provide a valid PCI AOC to FlexFactor can send raw card data (full PAN and CVV) directly in the /evaluate payload. Tokenization is the default recommended path, but it is not required when the integrator has PCI scope and an AOC on file.

PathWho it applies topaymentMethod.tokencardNumber
Tokenized (default)Any integrator — no PCI scope expansiontrueFlexFactor token (GUID from POST /v1/tokenize)
Direct card dataPCI-DSS certified integrators with valid AOCfalse (or omitted)Raw PAN

Evaluate payload difference — direct card data path

{
  "paymentMethod": {
    "cardNumber": "4111111111111111",
    "token": false,
    "verificationValue": "123",
    "cardBinNumber": "411111",
    "cardLast4Digits": "1111",
    "expirationMonth": 12,
    "expirationYear": 2028
  }
}

All other payload fields remain identical to the tokenized path.

FlexFactor must enable direct card data on the integrator account before it is active. Provide your PCI AOC to the FlexFactor integration team at [email protected] to request activation.


5. Error reference

HTTP CodeMeaningAction
200, 201, 204SuccessProcess response normally
400Validation errorParse errors object for field-level details — do not retry
401Invalid / expired tokenRefresh accessToken; retry once
403Access deniedCheck permissions — do not retry
405Method not allowedCheck HTTP method
500Server errorRetry with exponential backoff (max 3 attempts)
502Bad gatewayRetry with exponential backoff
503Service unavailableFall back to original decline — do not retry
504Gateway timeoutUpstream did not respond in time — retry with exponential backoff (max 3)

Error response shape

{
  "status": 400,
  "title": "One or more validation errors occurred.",
  "traceId": "00-64a5cf4492a4f17b...",
  "errors": {
    "transaction.amount": ["The field 'amount' must be greater than 0."]
  }
}

6. Sandbox vs production — known differences

ItemSandboxProduction
Base URLapi-sandbox.flexfactor.ioapi.flexfactor.io
AppKeySandbox-issued AppKeyProduction-issued AppKey
AppSecretSandbox-issued AppSecretProduction-issued AppSecret
MIDSandbox MID (GUID)Production MID (GUID)
SiteIDSandbox SiteID(s) scoped to sandbox MIDProduction SiteID(s) scoped to production MID
Webhook source IPs35.172.5.145, 3.211.16.18018.233.184.33, 52.4.241.101

AppKey, AppSecret, MID, and SiteID are all environment-specific — none carry over from sandbox to production. SiteIDs are scoped to their parent MID; a merchant with multiple brands or descriptors will have one SiteID per descriptor, and each must be provisioned separately in production. Ensure all four are swapped as part of the production cutover checklist.

For any behavioral differences between sandbox and production not covered in the published documentation, raise them directly with the FlexFactor integration team at [email protected].


7. Integration support

All technical integration questions: [email protected]