Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions mintlify/openapi.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

38 changes: 38 additions & 0 deletions mintlify/platform-overview/core-concepts/quote-system.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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"}'
```

<Note>
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.
</Note>

### Execution Timing

<Tabs>
Expand Down
49 changes: 49 additions & 0 deletions mintlify/platform-overview/core-concepts/transaction-lifecycle.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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 |
Expand All @@ -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
Expand Down Expand Up @@ -202,6 +206,7 @@ Outgoing payment webhooks use the format `OUTGOING_PAYMENT.<STATUS>`. The webhoo
| Event | Description |
|-------|-------------|
| `OUTGOING_PAYMENT.PENDING` | Transaction created, quote pending confirmation |
| `OUTGOING_PAYMENT.PENDING_AUTHORIZATION` | Awaiting Strong Customer Authentication (EU customers only) |
Comment thread
ls-bolt[bot] marked this conversation as resolved.
| `OUTGOING_PAYMENT.PROCESSING` | Quote confirmed, payout in progress |
| `OUTGOING_PAYMENT.COMPLETED` | Payout reached destination |
| `OUTGOING_PAYMENT.FAILED` | Payout failed |
Expand Down Expand Up @@ -229,6 +234,26 @@ Outgoing payment webhooks use the format `OUTGOING_PAYMENT.<STATUS>`. The webhoo
```
</Tab>

<Tab title="PENDING_AUTHORIZATION">
```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`.
</Tab>

<Tab title="COMPLETED">
```json
{
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -315,6 +347,21 @@ The standard successful payment flow:
3. `OUTGOING_PAYMENT.COMPLETED`
</Tab>

<Tab title="SCA Authorization (EU)">
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`

<Info>
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.
</Info>

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.
</Tab>

<Tab title="Failure with Refund">
Payment fails and the refund succeeds:

Expand Down Expand Up @@ -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':
Expand Down
5 changes: 3 additions & 2 deletions openapi.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -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 |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 |
Expand Down
1 change: 1 addition & 0 deletions openapi/components/schemas/webhooks/WebhookType.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
type: string
enum:
- OUTGOING_PAYMENT.PENDING
- OUTGOING_PAYMENT.PENDING_AUTHORIZATION
- OUTGOING_PAYMENT.PROCESSING
- OUTGOING_PAYMENT.COMPLETED
- OUTGOING_PAYMENT.FAILED
Expand Down
Loading