diff --git a/mintlify/openapi.yaml b/mintlify/openapi.yaml index d13da97fd..25844997c 100644 --- a/mintlify/openapi.yaml +++ b/mintlify/openapi.yaml @@ -19954,7 +19954,7 @@ components: |--------|-------------| | `CREATED` | Initial lookup has been created | | `PENDING` | Quote has been created | - | `PENDING_AUTHORIZATION` | Awaiting Strong Customer Authentication. Only occurs for customers in a region where SCA is required (e.g. EU); authorize the transaction's `scaChallenge` to proceed. | + | `PENDING_AUTHORIZATION` | Awaiting Strong Customer Authentication. Only occurs for customers in a region where SCA is required (e.g. EU). The challenge is carried by the quote, not the transaction — fetch `GET /quotes/{quoteId}` using the transaction's `quoteId`, then authorize its `scaChallenge` via `POST /quotes/{quoteId}/authorize`. | | `PROCESSING` | Funding has been received and payment initiated | | `COMPLETED` | Cross border payment has been received, converted and payment has been sent to the offramp network | | `REJECTED` | Receiving institution or wallet rejected payment, payment has been refunded | @@ -20377,7 +20377,7 @@ components: | Status | Description | |--------|-------------| | `PENDING` | Quote is pending confirmation | - | `PENDING_AUTHORIZATION` | Awaiting Strong Customer Authentication. Only occurs for customers in a region where SCA is required (e.g. EU); authorize the transaction's `scaChallenge` to proceed. | + | `PENDING_AUTHORIZATION` | Awaiting Strong Customer Authentication. Only occurs for customers in a region where SCA is required (e.g. EU). The challenge is carried by the quote, not the transaction — fetch `GET /quotes/{quoteId}` using the transaction's `quoteId`, then authorize its `scaChallenge` via `POST /quotes/{quoteId}/authorize`. | | `EXPIRED` | Quote wasn't executed before expiry window | | `PROCESSING` | Executing the quote after receiving funds | | `COMPLETED` | Payout successfully reached the destination | @@ -23318,6 +23318,7 @@ components: type: string enum: - OUTGOING_PAYMENT.PENDING + - OUTGOING_PAYMENT.PENDING_AUTHORIZATION - OUTGOING_PAYMENT.PROCESSING - OUTGOING_PAYMENT.COMPLETED - OUTGOING_PAYMENT.FAILED diff --git a/mintlify/platform-overview/core-concepts/quote-system.mdx b/mintlify/platform-overview/core-concepts/quote-system.mdx index 13d7f6657..a85a5ac24 100644 --- a/mintlify/platform-overview/core-concepts/quote-system.mdx +++ b/mintlify/platform-overview/core-concepts/quote-system.mdx @@ -228,6 +228,44 @@ curl -X POST https://api.lightspark.com/grid/2025-10-13/quotes/Quote:abc123/exec } ``` +### Strong Customer Authentication (EU Customers) + +For customers in regions where Strong Customer Authentication (SCA) is required (e.g., EU), quotes may enter a `PENDING_AUTHORIZATION` status with an `scaChallenge` object. The transfer is not released until the customer authorizes it. + +Where the challenge appears depends on how the quote is funded: + +- **Prefunded** — `POST /quotes/{quoteId}/execute` returns `PENDING_AUTHORIZATION` with the challenge instead of initiating the transfer. The challenge does not exist until `execute` is called, so the proof always goes on the follow-up authorize; re-calling `execute` returns `409`. +- **Realtime (JIT) funded** — the challenge arrives earlier and `paymentInstructions` are withheld until it is satisfied. + +**Quote response with SCA challenge:** + +```json +{ + "id": "Quote:019542f5-b3e7-1d02-0000-000000000050", + "status": "PENDING_AUTHORIZATION", + "scaChallenge": { + "id": "ScaChallenge:...", + "expiresAt": "2025-10-03T12:05:00Z", + "factor": "SMS_OTP", + "availableFactors": ["SMS_OTP", "PASSKEY"], + "purpose": "PAYOUT" + } +} +``` + +To authorize the quote, call `POST /quotes/{quoteId}/authorize` with the appropriate proof: + +```bash +curl -X POST https://api.lightspark.com/grid/2025-10-13/quotes/Quote:abc123/authorize \ + -u "$GRID_CLIENT_ID:$GRID_CLIENT_SECRET" \ + -H "Content-Type: application/json" \ + -d '{"code": "123456"}' +``` + + + Multiple authorizations may be required in sequence (e.g., currency conversion + payout). After each authorization, check the quote status — if still `PENDING_AUTHORIZATION`, authorize the next challenge. + + ### Execution Timing diff --git a/mintlify/platform-overview/core-concepts/transaction-lifecycle.mdx b/mintlify/platform-overview/core-concepts/transaction-lifecycle.mdx index 37197e1ae..34421eedb 100644 --- a/mintlify/platform-overview/core-concepts/transaction-lifecycle.mdx +++ b/mintlify/platform-overview/core-concepts/transaction-lifecycle.mdx @@ -121,6 +121,7 @@ A single `status` field represents whether the transaction reached its destinati | Status | Description | |--------|-------------| | **PENDING** | Quote is pending confirmation | +| **PENDING_AUTHORIZATION** | Awaiting Strong Customer Authentication. Only occurs for customers in regions where SCA is required (e.g., EU). The challenge lives on the quote, not the transaction — fetch `GET /quotes/{quoteId}` using the transaction's `quoteId`, then authorize its `scaChallenge` with `POST /quotes/{quoteId}/authorize`. | | **EXPIRED** | Quote wasn't executed before the expiry window | | **PROCESSING** | Executing the quote after receiving funds (checking internal balances, or push/pull to/from external account) | | **COMPLETED** | Payout successfully reached the destination account | @@ -135,6 +136,9 @@ A single `status` field represents whether the transaction reached its destinati ```mermaid stateDiagram-v2 [*] --> PENDING : Quote created + PENDING --> PENDING_AUTHORIZATION : SCA required (EU) + PENDING_AUTHORIZATION --> PROCESSING : Authorized + PENDING_AUTHORIZATION --> FAILED : Challenge expired unsatisfied PENDING --> PROCESSING : Quote confirmed, funds received PENDING --> EXPIRED : Quote expired before execution PROCESSING --> COMPLETED : Payout reached destination @@ -202,6 +206,7 @@ Outgoing payment webhooks use the format `OUTGOING_PAYMENT.`. The webhoo | Event | Description | |-------|-------------| | `OUTGOING_PAYMENT.PENDING` | Transaction created, quote pending confirmation | +| `OUTGOING_PAYMENT.PENDING_AUTHORIZATION` | Awaiting Strong Customer Authentication (EU customers only) | | `OUTGOING_PAYMENT.PROCESSING` | Quote confirmed, payout in progress | | `OUTGOING_PAYMENT.COMPLETED` | Payout reached destination | | `OUTGOING_PAYMENT.FAILED` | Payout failed | @@ -229,6 +234,26 @@ Outgoing payment webhooks use the format `OUTGOING_PAYMENT.`. The webhoo ``` + +```json +{ + "type": "OUTGOING_PAYMENT.PENDING_AUTHORIZATION", + "data": { + "id": "Transaction:...", + "status": "PENDING_AUTHORIZATION", + "type": "OUTGOING", + "customerId": "Customer:019542f5-b3e7-1d02-0000-000000000001", + "quoteId": "Quote:019542f5-b3e7-1d02-0000-000000000006", + "sentAmount": {"amount": 100000, "currency": {"code": "USD"}}, + "receivedAmount": {"amount": 92000, "currency": {"code": "EUR"}}, + "createdAt": "2025-10-03T15:00:00Z" + } +} +``` + +The payload carries no `scaChallenge` — that field lives on the quote. Use `quoteId` to fetch `GET /quotes/{quoteId}`, read its `scaChallenge`, and authorize with `POST /quotes/{quoteId}/authorize`. + + ```json { @@ -282,6 +307,13 @@ app.post('/webhooks/grid', async (req, res) => { const { data, type } = req.body; switch (type) { + case 'OUTGOING_PAYMENT.PENDING_AUTHORIZATION': { + // EU customers only: the challenge is on the quote, not the transaction + const quote = await getQuote(data.quoteId); + await promptScaChallenge(data.customerId, data.quoteId, quote.scaChallenge); + break; + } + case 'OUTGOING_PAYMENT.COMPLETED': await notifyCustomer(data.customerId, 'Payment delivered!'); break; @@ -315,6 +347,21 @@ The standard successful payment flow: 3. `OUTGOING_PAYMENT.COMPLETED` + +The customer must satisfy a Strong Customer Authentication challenge before the payout proceeds: + +1. `OUTGOING_PAYMENT.PENDING` +2. `OUTGOING_PAYMENT.PENDING_AUTHORIZATION` +3. `OUTGOING_PAYMENT.PROCESSING` +4. `OUTGOING_PAYMENT.COMPLETED` + + +A single payment can require more than one authorization in sequence, so `OUTGOING_PAYMENT.PENDING_AUTHORIZATION` may arrive several times before `PROCESSING`. Loop on the quote's status rather than assuming one authorization releases the payment. + + +If the challenge expires before the customer satisfies it, the transaction lands on `OUTGOING_PAYMENT.FAILED` with `failureReason: SCA_NOT_COMPLETED` and no funds move. Create a new quote to try again. + + Payment fails and the refund succeeds: @@ -455,6 +502,8 @@ When a transaction fails, a refund is initiated automatically. Track the refund switch (webhookType) { case 'OUTGOING_PAYMENT.PENDING': return 'Payment processing...'; + case 'OUTGOING_PAYMENT.PENDING_AUTHORIZATION': + return 'Authorization required. Complete the security challenge to continue.'; case 'OUTGOING_PAYMENT.PROCESSING': return 'Payment in progress...'; case 'OUTGOING_PAYMENT.COMPLETED': diff --git a/openapi.yaml b/openapi.yaml index d13da97fd..25844997c 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -19954,7 +19954,7 @@ components: |--------|-------------| | `CREATED` | Initial lookup has been created | | `PENDING` | Quote has been created | - | `PENDING_AUTHORIZATION` | Awaiting Strong Customer Authentication. Only occurs for customers in a region where SCA is required (e.g. EU); authorize the transaction's `scaChallenge` to proceed. | + | `PENDING_AUTHORIZATION` | Awaiting Strong Customer Authentication. Only occurs for customers in a region where SCA is required (e.g. EU). The challenge is carried by the quote, not the transaction — fetch `GET /quotes/{quoteId}` using the transaction's `quoteId`, then authorize its `scaChallenge` via `POST /quotes/{quoteId}/authorize`. | | `PROCESSING` | Funding has been received and payment initiated | | `COMPLETED` | Cross border payment has been received, converted and payment has been sent to the offramp network | | `REJECTED` | Receiving institution or wallet rejected payment, payment has been refunded | @@ -20377,7 +20377,7 @@ components: | Status | Description | |--------|-------------| | `PENDING` | Quote is pending confirmation | - | `PENDING_AUTHORIZATION` | Awaiting Strong Customer Authentication. Only occurs for customers in a region where SCA is required (e.g. EU); authorize the transaction's `scaChallenge` to proceed. | + | `PENDING_AUTHORIZATION` | Awaiting Strong Customer Authentication. Only occurs for customers in a region where SCA is required (e.g. EU). The challenge is carried by the quote, not the transaction — fetch `GET /quotes/{quoteId}` using the transaction's `quoteId`, then authorize its `scaChallenge` via `POST /quotes/{quoteId}/authorize`. | | `EXPIRED` | Quote wasn't executed before expiry window | | `PROCESSING` | Executing the quote after receiving funds | | `COMPLETED` | Payout successfully reached the destination | @@ -23318,6 +23318,7 @@ components: type: string enum: - OUTGOING_PAYMENT.PENDING + - OUTGOING_PAYMENT.PENDING_AUTHORIZATION - OUTGOING_PAYMENT.PROCESSING - OUTGOING_PAYMENT.COMPLETED - OUTGOING_PAYMENT.FAILED diff --git a/openapi/components/schemas/transactions/OutgoingTransactionStatus.yaml b/openapi/components/schemas/transactions/OutgoingTransactionStatus.yaml index d49ffd374..e23f5e263 100644 --- a/openapi/components/schemas/transactions/OutgoingTransactionStatus.yaml +++ b/openapi/components/schemas/transactions/OutgoingTransactionStatus.yaml @@ -12,7 +12,7 @@ description: | | Status | Description | |--------|-------------| | `PENDING` | Quote is pending confirmation | - | `PENDING_AUTHORIZATION` | Awaiting Strong Customer Authentication. Only occurs for customers in a region where SCA is required (e.g. EU); authorize the transaction's `scaChallenge` to proceed. | + | `PENDING_AUTHORIZATION` | Awaiting Strong Customer Authentication. Only occurs for customers in a region where SCA is required (e.g. EU). The challenge is carried by the quote, not the transaction — fetch `GET /quotes/{quoteId}` using the transaction's `quoteId`, then authorize its `scaChallenge` via `POST /quotes/{quoteId}/authorize`. | | `EXPIRED` | Quote wasn't executed before expiry window | | `PROCESSING` | Executing the quote after receiving funds | | `COMPLETED` | Payout successfully reached the destination | diff --git a/openapi/components/schemas/transactions/TransactionStatus.yaml b/openapi/components/schemas/transactions/TransactionStatus.yaml index dc5524c0f..767c3176f 100644 --- a/openapi/components/schemas/transactions/TransactionStatus.yaml +++ b/openapi/components/schemas/transactions/TransactionStatus.yaml @@ -16,7 +16,7 @@ description: | |--------|-------------| | `CREATED` | Initial lookup has been created | | `PENDING` | Quote has been created | - | `PENDING_AUTHORIZATION` | Awaiting Strong Customer Authentication. Only occurs for customers in a region where SCA is required (e.g. EU); authorize the transaction's `scaChallenge` to proceed. | + | `PENDING_AUTHORIZATION` | Awaiting Strong Customer Authentication. Only occurs for customers in a region where SCA is required (e.g. EU). The challenge is carried by the quote, not the transaction — fetch `GET /quotes/{quoteId}` using the transaction's `quoteId`, then authorize its `scaChallenge` via `POST /quotes/{quoteId}/authorize`. | | `PROCESSING` | Funding has been received and payment initiated | | `COMPLETED` | Cross border payment has been received, converted and payment has been sent to the offramp network | | `REJECTED` | Receiving institution or wallet rejected payment, payment has been refunded | diff --git a/openapi/components/schemas/webhooks/WebhookType.yaml b/openapi/components/schemas/webhooks/WebhookType.yaml index fab72c524..da61ac7f7 100644 --- a/openapi/components/schemas/webhooks/WebhookType.yaml +++ b/openapi/components/schemas/webhooks/WebhookType.yaml @@ -1,6 +1,7 @@ type: string enum: - OUTGOING_PAYMENT.PENDING + - OUTGOING_PAYMENT.PENDING_AUTHORIZATION - OUTGOING_PAYMENT.PROCESSING - OUTGOING_PAYMENT.COMPLETED - OUTGOING_PAYMENT.FAILED