Skip to content

Commit 2b4dbb6

Browse files
authored
docs: add outgoing payment states, refund object, and webhook scenarios (#340)
1 parent 2ed77c9 commit 2b4dbb6

16 files changed

Lines changed: 421 additions & 387 deletions

File tree

mintlify/global-p2p/onboarding-customers/configuring-customers.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -249,7 +249,7 @@ CSV Upload Best Practices
249249
You can track the job status through:
250250

251251
1. Webhook notifications (if configured)
252-
2. Status polling endpoint:
252+
2. Status endpoint:
253253

254254
```bash
255255
curl -sS -X GET "https://api.lightspark.com/grid/2025-10-13/customers/bulk/jobs/{jobId}" \

mintlify/payouts-and-b2b/onboarding/configuring-customers.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -230,7 +230,7 @@ CSV Upload Best Practices
230230
You can track the job status through:
231231

232232
1. Webhook notifications (if configured)
233-
2. Status polling endpoint:
233+
2. Status endpoint:
234234

235235
```bash
236236
curl -X GET "https://api.lightspark.com/grid/2025-10-13/customers/bulk/jobs/{jobId}" \

mintlify/payouts-and-b2b/payment-flow/send-payment.mdx

Lines changed: 61 additions & 140 deletions
Original file line numberDiff line numberDiff line change
@@ -125,32 +125,30 @@ curl -X POST 'https://api.lightspark.com/grid/2025-10-13/transfer-out' \
125125
</Step>
126126

127127
<Step title="Track transfer status">
128-
The transaction is created with a `PENDING` status. Monitor the status by:
128+
The transaction is created with a `PENDING` status and progresses through `PROCESSING` to `COMPLETED` or `FAILED`. Monitor the status by:
129129

130-
**Option 1: Webhook notifications** (recommended)
130+
You'll receive `OUTGOING_PAYMENT.<STATUS>` webhooks as the transaction progresses. The webhook body contains the full transaction resource:
131131

132132
```json
133133
{
134-
"type": "OUTGOING_PAYMENT",
135-
"transaction": {
134+
"type": "OUTGOING_PAYMENT.COMPLETED",
135+
"data": {
136136
"id": "Transaction:019542f5-b3e7-1d02-0000-000000000015",
137137
"status": "COMPLETED",
138+
"type": "OUTGOING",
139+
"sentAmount": { "amount": 12550, "currency": { "code": "USD", "decimals": 2 } },
140+
"receivedAmount": { "amount": 12550, "currency": { "code": "USD", "decimals": 2 } },
138141
"settledAt": "2025-10-03T15:02:30Z"
139142
},
140143
"timestamp": "2025-10-03T15:03:00Z"
141144
}
142145
```
143146

144-
**Option 2: Poll the transaction endpoint**
147+
If a transaction fails, Grid initiates a refund automatically. You'll receive `OUTGOING_PAYMENT.REFUND_PENDING` followed by `OUTGOING_PAYMENT.REFUND_COMPLETED` or `OUTGOING_PAYMENT.REFUND_FAILED`. The transaction's `refund` object tracks the refund status and reference.
145148

146-
```bash
147-
curl -X GET 'https://api.lightspark.com/grid/2025-10-13/transactions/Transaction:019542f5-b3e7-1d02-0000-000000000015' \
148-
-H 'Authorization: Basic $GRID_CLIENT_ID:$GRID_CLIENT_SECRET'
149-
```
150-
151-
<Check>
152-
When the transaction status changes to `COMPLETED`, the funds have been successfully transferred to the external account.
153-
</Check>
149+
<Info>
150+
For the full state diagram, refund object details, and all webhook scenarios (including bank returns and manual cancellations), see the [Transaction Lifecycle](/platform-overview/core-concepts/transaction-lifecycle) guide.
151+
</Info>
154152
</Step>
155153
</Steps>
156154

@@ -159,9 +157,14 @@ curl -X GET 'https://api.lightspark.com/grid/2025-10-13/transactions/Transaction
159157
| Status | Description |
160158
| ------------ | --------------------------------------------- |
161159
| `PENDING` | Transfer initiated and awaiting processing |
160+
| `EXPIRED` | Quote wasn't executed before the expiry window |
162161
| `PROCESSING` | Transfer in progress through the payment rail |
163162
| `COMPLETED` | Transfer successfully completed |
164-
| `FAILED` | Transfer failed (see error details) |
163+
| `FAILED` | Transfer failed — accompanied by a `failureReason` |
164+
165+
<Info>
166+
For the full state diagram including refund tracking and edge cases like bank returns, see the [Transaction Lifecycle](/platform-overview/core-concepts/transaction-lifecycle) guide.
167+
</Info>
165168

166169
## Cross-Currency Transfers
167170

@@ -259,7 +262,7 @@ curl -X POST 'https://api.lightspark.com/grid/2025-10-13/quotes' \
259262
- Quote hasn't expired (check `expiresAt`)
260263

261264
<Warning>
262-
Quotes typically expire after 15 minutes. If expired, create a new quote to get an updated exchange rate.
265+
Quote expiration depends on the corridor but is typically ~5 minutes or greater. If expired, create a new quote to get an updated exchange rate.
263266
</Warning>
264267
</Step>
265268

@@ -309,24 +312,22 @@ curl -X POST 'https://api.lightspark.com/grid/2025-10-13/quotes/Quote:019542f5-b
309312
<Check>
310313
Once executed, the quote creates a transaction and the transfer begins processing. The `transactionId` can be used to track the payment.
311314
</Check>
315+
316+
<Info>
317+
**Real-time funding sources:** If your quote uses a real-time funding source (USDC, BTC, RTP, or FedNow), you don't call the execute endpoint. Instead, send a payment to the account specified in the quote's `paymentInstructions`. Grid detects the deposit and processes the transfer automatically.
318+
</Info>
312319
</Step>
313320

314321
<Step title="Monitor completion">
315-
Track the transfer using webhooks or by polling the transaction:
316-
317-
```bash
318-
curl -X GET 'https://api.lightspark.com/grid/2025-10-13/transactions/Transaction:019542f5-b3e7-1d02-0000-000000000030' \
319-
-H 'Authorization: Basic $GRID_CLIENT_ID:$GRID_CLIENT_SECRET'
320-
```
321-
322-
You'll receive a webhook when the transaction completes:
322+
After execution, a transaction is created and progresses through `PENDING``PROCESSING``COMPLETED` or `FAILED`. You'll receive `OUTGOING_PAYMENT.<STATUS>` webhooks as the transaction progresses:
323323

324324
```json
325325
{
326-
"type": "OUTGOING_PAYMENT",
327-
"transaction": {
326+
"type": "OUTGOING_PAYMENT.COMPLETED",
327+
"data": {
328328
"id": "Transaction:019542f5-b3e7-1d02-0000-000000000030",
329329
"status": "COMPLETED",
330+
"type": "OUTGOING",
330331
"sentAmount": {
331332
"amount": 10000,
332333
"currency": { "code": "USD", "symbol": "$", "decimals": 2 }
@@ -343,49 +344,57 @@ You'll receive a webhook when the transaction completes:
343344
}
344345
```
345346

347+
If a transaction fails, Grid initiates a refund automatically. You'll receive `OUTGOING_PAYMENT.REFUND_PENDING` followed by `OUTGOING_PAYMENT.REFUND_COMPLETED` or `OUTGOING_PAYMENT.REFUND_FAILED`. The transaction's `refund` object tracks the refund status and reference.
348+
349+
<Info>
350+
For the full state diagram, refund object details, and all webhook scenarios (including bank returns and manual cancellations), see the [Transaction Lifecycle](/platform-overview/core-concepts/transaction-lifecycle) guide.
351+
</Info>
346352
</Step>
347353
</Steps>
348354

349-
### Quote statuses
355+
### Transaction statuses
350356

351357
| Status | Description |
352358
| ------------ | ------------------------------------------ |
353359
| `PENDING` | Quote created, awaiting execution |
354360
| `PROCESSING` | Quote executed, transfer in progress |
355361
| `COMPLETED` | Transfer successfully completed |
356-
| `FAILED` | Transfer failed (funds returned to source) |
362+
| `FAILED` | Transfer failed — refund initiated automatically (track via `refund` object) |
357363
| `EXPIRED` | Quote expired without execution |
358364

359365
## Checking Payment Status
360366

361-
### Using webhooks (recommended)
362-
363367
Configure a webhook endpoint to receive real-time notifications when payment status changes:
364368

365369
```javascript
366-
// Your webhook endpoint
367370
app.post("/webhooks/grid", (req, res) => {
368-
const { type, transaction } = req.body;
369-
370-
if (type === "OUTGOING_PAYMENT") {
371-
const { id, status, settledAt } = transaction;
372-
373-
switch (status) {
374-
case "COMPLETED":
375-
console.log(`Payment ${id} completed at ${settledAt}`);
376-
// Update your database, notify customer, etc.
377-
break;
378-
379-
case "FAILED":
380-
console.log(`Payment ${id} failed`);
381-
// Handle failure, refund, notify customer
382-
break;
383-
384-
case "PROCESSING":
385-
console.log(`Payment ${id} is processing`);
386-
// Optional: Update UI to show processing state
387-
break;
388-
}
371+
const { type, data } = req.body;
372+
373+
switch (type) {
374+
case "OUTGOING_PAYMENT.COMPLETED":
375+
console.log(`Payment ${data.id} completed at ${data.settledAt}`);
376+
// Update your database, notify customer
377+
break;
378+
379+
case "OUTGOING_PAYMENT.FAILED":
380+
console.log(`Payment ${data.id} failed: ${data.failureReason}`);
381+
// Handle failure, notify customer — refund webhook follows
382+
break;
383+
384+
case "OUTGOING_PAYMENT.PROCESSING":
385+
console.log(`Payment ${data.id} is processing`);
386+
// Optional: Update UI to show processing state
387+
break;
388+
389+
case "OUTGOING_PAYMENT.REFUND_COMPLETED":
390+
console.log(`Payment ${data.id} refund completed`);
391+
// Update your records with refund details
392+
break;
393+
394+
case "OUTGOING_PAYMENT.REFUND_FAILED":
395+
console.log(`Payment ${data.id} refund failed`);
396+
// Alert your team — may require manual resolution
397+
break;
389398
}
390399

391400
res.status(200).json({ received: true });
@@ -397,99 +406,11 @@ app.post("/webhooks/grid", (req, res) => {
397406
webhook implementation details including signature verification.
398407
</Tip>
399408

400-
### Polling transactions
401-
402-
If webhooks aren't available, poll the transaction endpoint:
403-
404-
```javascript
405-
async function checkPaymentStatus(transactionId) {
406-
const response = await fetch(
407-
`https://api.lightspark.com/grid/2025-10-13/transactions/${transactionId}`,
408-
{
409-
headers: {
410-
Authorization: `Basic ${credentials}`,
411-
},
412-
}
413-
);
414-
415-
const transaction = await response.json();
416-
return transaction.status;
417-
}
418-
419-
// Poll every 10 seconds
420-
const pollInterval = setInterval(async () => {
421-
const status = await checkPaymentStatus(
422-
"Transaction:019542f5-b3e7-1d02-0000-000000000015"
423-
);
424-
425-
if (status === "COMPLETED" || status === "FAILED") {
426-
clearInterval(pollInterval);
427-
// Handle completion
428-
}
429-
}, 10000);
430-
```
431-
432-
<Warning>
433-
Polling can delay status updates and increase API usage. Use webhooks for
434-
production applications whenever possible.
435-
</Warning>
436-
437409
## Best Practices
438410

439411
<AccordionGroup>
440-
<Accordion title="Always check account balance before transfers">
441-
Verify the internal account has sufficient balance to avoid failed transfers:
442-
443-
```javascript
444-
async function checkBalance(accountId, requiredAmount) {
445-
const response = await fetch(
446-
`https://api.lightspark.com/grid/2025-10-13/customers/internal-accounts`,
447-
{
448-
headers: { Authorization: `Basic ${credentials}` },
449-
}
450-
);
451-
452-
const { data } = await response.json();
453-
const account = data.find((a) => a.id === accountId);
454-
455-
if (account.balance.amount < requiredAmount) {
456-
throw new Error("Insufficient balance");
457-
}
458-
459-
return true;
460-
}
461-
```
462-
463-
This prevents unnecessary API calls and provides better user feedback.
464-
465-
</Accordion>
466-
467-
<Accordion title="Use idempotency for quote execution">
468-
Store quote IDs in your database to prevent accidental duplicate executions:
469-
470-
```javascript
471-
async function executeQuoteOnce(quoteId) {
472-
// Check if already executed
473-
const existing = await db.quotes.findOne({ quoteId });
474-
if (existing?.executed) {
475-
throw new Error("Quote already executed");
476-
}
477-
478-
// Execute and mark as executed
479-
const result = await executeQuote(quoteId);
480-
await db.quotes.update(
481-
{ quoteId },
482-
{ executed: true, transactionId: result.transactionId }
483-
);
484-
485-
return result;
486-
}
487-
```
488-
489-
</Accordion>
490-
491412
<Accordion title="Handle quote expiration gracefully">
492-
Quotes expire after a short period. Always check expiration before executing:
413+
Quote expiration depends on the corridor (typically ~5 minutes or greater). Always check expiration before executing:
493414

494415
```javascript
495416
async function executeQuoteWithCheck(quoteId) {

mintlify/payouts-and-b2b/quickstart.mdx

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -380,9 +380,7 @@ curl -X POST "https://api.lightspark.com/grid/2025-10-13/quotes/Quote:019542f5-b
380380
}
381381
```
382382

383-
The quote status changes to `PROCESSING` and the transfer is initiated. You can track the status by:
384-
1. Polling the quote endpoint: `GET /quotes/{quoteId}`
385-
2. Waiting for webhook notifications
383+
The quote status changes to `PROCESSING` and the transfer is initiated. You'll receive a webhook when the transfer completes:
386384

387385
**Completion Webhook:**
388386
```json

0 commit comments

Comments
 (0)