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).
| Credential | Type | Required | Description |
|---|---|---|---|
| MID | GUID | Required | FlexFactor Merchant Identification Number. Identifies the merchant account on every API call. |
| API Key | String | Required | Public client key (the AppKey) sent with the API Secret to /oauth2/token to obtain a Bearer token. |
| API Secret | String | Required | Client secret (the AppSecret) paired with the API Key to authenticate. Store securely server-side; never expose it to the client. |
| Tokenization Key | String | Optional | Used only by the /tokenize API — passed as the environment query parameter to vault card data. Not sent on any other call. |
| Site ID | GUID | Optional | Identifies 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
| Environment | Base URL |
|---|---|
| Sandbox | https://api-sandbox.flexfactor.io/v1/ |
| Production | https://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
| Field | Rank | Note |
|---|---|---|
paymentMethod.verificationValue | Critical | Directly drives rescue performance — include whenever available; absence has measurable negative impact |
customerIp | Critical (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, cardCountry | Required | Required fields; each contributes meaningful rescue signal |
paymentMethod.holderName | Required | Required per field reference |
transaction.responseCode, responseCodeSource, processorName | High | Significantly improve rescue performance; not required for launch |
deviceDetails (all child fields) | High | Significantly 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
deviceDetailsis 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"}
resultreflects whether the API request succeeded (SUCCESS/FAILED).statusreflects the transaction outcome. Always key your integration logic onstatus.
transaction.amountis 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.
orderIdin 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. Asynchronous — POST /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" }
}
expiryDateUtcis the deadline by which FlexFactor must complete the rescue — recommended up to 21 days. After this date the order moves toEXPIRED.
customerIpis 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 fullsubscriptionobject is strongly recommended for all MIT flows.
Response
{"result": "SUCCESS", "status": "SUBMITTED", "orderId": "ff-3a7b2c1d-..."}Webhook resolution
| Webhook | Meaning | Action |
|---|---|---|
order.completed | Rescue approved — funds captured | Mark order as paid |
order.cancelled | Rescue declined — no further attempts | Return to dunning or write off |
order.expired | expiryDateUtc passed without resolution | Treat 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 MIT | Sync MIT | |
|---|---|---|
expiryDateUtc | Required | Omit |
| Response | Always SUBMITTED | APPROVED / DECLINED / SUBMITTED |
| Resolution | Webhooks | Synchronous (may still fall back to webhook) |
transactionType effect | Respected | Auth/capture always, regardless of transactionType |
Retry transaction.id | Flexible | Must 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, orCAPTUREREQUIREDstate - CIT orders in
CAPTUREREQUIREDstate
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:
| Endpoint | ID used | When to use |
|---|---|---|
POST /v1/orders/{orderId}/cancel | FlexFactor order GUID | Primary — standard cancellation once orderId is known |
POST /v1/orders/{external-order-id}/external-cancel | Merchant's own order ID | Timeout 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"
}
amountToRefundis in dollars, not cents. Partial refunds are supported. Double-refunds are blocked at the platform level.
refundMessageis 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
| Scenario | Behavior |
|---|---|
Retry with same idempotencyKey | FlexFactor returns the identical response — no duplicate order is created |
New idempotencyKey on retry | A 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 filters | Search by your external orderId to find orders when the FF orderId was never received |
The
idempotencyKeyis 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 Code | Recommended action |
|---|---|
200 / 201 | Process response normally — no retry needed |
401 | Refresh access token; retry the request once |
500 / 502 / 504 | Retry with exponential backoff — max 3 attempts |
503 | Do not retry — fall back to original decline |
400 | Do not retry — fix the request payload |
| Timeout / no response | Retry 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:
status | statusName | Meaning | Action |
|---|---|---|---|
| 0 | Draft | MIT order received, not yet processing | Pending — await webhook / keep polling |
| 1 | Onhold | Order temporarily on hold | Pending — keep polling |
| 2 | Cancelled | Order was cancelled (voided) | Treat as not rescued |
| 3 | Problem | A processing problem occurred | Treat as not rescued; investigate |
| 4 | Processing | Rescue in progress | Pending — await webhook / keep polling |
| 5 | Capturerequired | Auth succeeded; capture required | Call /capture to complete |
| 6 | Returned | Order was refunded/returned | Treat as refunded |
| 7 | Completed | Rescue succeeded — funds captured | Mark as paid |
An MIT order that exceeds its
expiryDateUtcresolves toCancelled(status2); theorder.expiredwebhook still fires so you can distinguish expiry from a merchant/system cancellation.
The
/evaluateresponsestatus(APPROVED,DECLINED,CHALLENGE,SUBMITTED,CAPTUREREQUIRED) is a separate field from the orderstatusNameabove —GET /ordersdoes 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.
| Path | Who it applies to | paymentMethod.token | cardNumber |
|---|---|---|---|
| Tokenized (default) | Any integrator — no PCI scope expansion | true | FlexFactor token (GUID from POST /v1/tokenize) |
| Direct card data | PCI-DSS certified integrators with valid AOC | false (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 Code | Meaning | Action |
|---|---|---|
| 200, 201, 204 | Success | Process response normally |
| 400 | Validation error | Parse errors object for field-level details — do not retry |
| 401 | Invalid / expired token | Refresh accessToken; retry once |
| 403 | Access denied | Check permissions — do not retry |
| 405 | Method not allowed | Check HTTP method |
| 500 | Server error | Retry with exponential backoff (max 3 attempts) |
| 502 | Bad gateway | Retry with exponential backoff |
| 503 | Service unavailable | Fall back to original decline — do not retry |
| 504 | Gateway timeout | Upstream 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
| Item | Sandbox | Production |
|---|---|---|
| Base URL | api-sandbox.flexfactor.io | api.flexfactor.io |
| AppKey | Sandbox-issued AppKey | Production-issued AppKey |
| AppSecret | Sandbox-issued AppSecret | Production-issued AppSecret |
| MID | Sandbox MID (GUID) | Production MID (GUID) |
| SiteID | Sandbox SiteID(s) scoped to sandbox MID | Production SiteID(s) scoped to production MID |
| Webhook source IPs | 35.172.5.145, 3.211.16.180 | 18.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]
Updated about 20 hours ago
