> ## Documentation Index
> Fetch the complete documentation index at: https://ramps-claude-transfer-api-deprecation-docs-6ycamq.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Sending Payments

> Learn how to send payments between accounts

export const FeatureCardGrid = ({cols = 3, children}) => <div className={`not-prose feature-cards-grid feature-cards-cols-${cols}`}>
    {children}
  </div>;

export const FeatureCard = ({icon, title, children, href, linkHref, linkText, color, tag, tagPosition, layout, variant, iconSize}) => {
  const isHorizontal = layout === 'horizontal';
  const isFlat = variant === 'flat';
  const isLargeIcon = iconSize === 'lg';
  const isInlineTag = tagPosition === 'inline';
  const card = <div className={`feature-card ${href ? 'feature-card-link' : ''} ${!icon ? 'feature-card-no-icon' : ''} ${isHorizontal ? 'feature-card-horizontal' : ''} ${isFlat ? 'feature-card-flat' : ''} ${isLargeIcon ? 'feature-card-icon-lg' : ''}`}>
      {icon && <div className="feature-card-icon-wrapper">
          {color ? <div className="feature-card-icon" style={{
    WebkitMaskImage: `url(${icon})`,
    maskImage: `url(${icon})`,
    backgroundColor: color,
    width: '24px',
    height: '24px',
    WebkitMaskSize: 'contain',
    maskSize: 'contain',
    WebkitMaskRepeat: 'no-repeat',
    maskRepeat: 'no-repeat'
  }} /> : <img src={icon} alt="" className="feature-card-icon" />}
        </div>}
      <div className="feature-card-content">
        {isInlineTag ? <div className="feature-card-title-row">
            <span className="feature-card-title">{title}</span>
            {tag && <span className="feature-card-tag">{tag}</span>}
          </div> : <div className="feature-card-title">{title}</div>}
        <div className="feature-card-desc">{children}</div>
        {tag && !isInlineTag && <div className="feature-card-tag-row"><span className="feature-card-tag">{tag}</span></div>}
        {linkText && <div className="feature-card-link-row">
            {linkHref ? <a href={linkHref} className="feature-card-text-link" style={{
    color: color
  }}>
                {linkText}
              </a> : <span className="feature-card-text-link feature-card-coming-soon" style={{
    color: color,
    opacity: 0.6
  }}>
                {linkText}
              </span>}
          </div>}
      </div>
    </div>;
  return href ? <a href={href} className="feature-card-anchor">{card}</a> : card;
};

Send payments between internal and external accounts, with automatic exchange rate handling when the currencies differ.

## Overview

Every payment goes through `POST /quotes`, whether or not the currencies differ. A quote
prices the transfer — the amounts, the fees, and, when the currencies differ, the exchange
rate — and creates the transaction that carries the money.

What varies is when you execute it:

* **In one request.** Set `immediatelyExecute` and Grid creates and executes the quote
  together. Use this when you don't need to put rate or fee details in front of your user
  before the money moves.
* **In two steps.** Create the quote, show your user what the transfer will cost, then call
  execute before the quote expires. Use this whenever your UX surfaces rates or fees — which
  includes same-currency transfers, where there is no exchange rate but there can still be
  fees worth showing.

Either way the request shape is the same, and the payment rail is chosen from the
destination account.

The same endpoint sends to UMA addresses by giving the quote a `UMA_ADDRESS` destination.
See [Sending payments](/global-p2p/sending-receiving-payments/sending-payments) for that flow.

## Prerequisites

Before sending payments, ensure you have:

* An active internal account with sufficient balance
* A verified external account for the destination
* Valid API credentials with appropriate permissions
* A webhook endpoint configured to receive payment status updates (recommended)

<Tip>
  If you don't have these set up yet, review the [Internal
  Accounts](/payouts-and-b2b/depositing-funds/internal-accounts) and [External
  Accounts](/payouts-and-b2b/depositing-funds/external-accounts) guides first.
</Tip>

## Send a payment

<Steps>
  <Step title="Get account IDs">
    Retrieve the internal account (source) and external account (destination) IDs:

    ```bash theme={null}
    curl -X GET 'https://api.lightspark.com/grid/2025-10-13/customers/internal-accounts?customerId=Customer:019542f5-b3e7-1d02-0000-000000000001' \
      -u "$GRID_CLIENT_ID:$GRID_CLIENT_SECRET"
    ```

    Note the `id` fields from both the internal and external accounts you want to use.
  </Step>

  <Step title="Create the quote">
    Specify the source and destination accounts and the amount to lock:

    ```bash cURL theme={null}
    curl -X POST 'https://api.lightspark.com/grid/2025-10-13/quotes' \
      -u "$GRID_CLIENT_ID:$GRID_CLIENT_SECRET" \
      -H 'Content-Type: application/json' \
      -d '{
        "source": {
          "sourceType": "ACCOUNT",
          "accountId": "InternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965"
        },
        "destination": {
          "destinationType": "ACCOUNT",
          "accountId": "ExternalAccount:a12dcbd6-dced-4ec4-b756-3c3a9ea3d123",
          "paymentRail": "ACH"
        },
        "lockedCurrencySide": "SENDING",
        "lockedCurrencyAmount": 10000,
        "remittanceInformation": "INV-12345",
        "description": "Payment for services - Invoice #1234"
      }'
    ```

    ```json Success (201 Created) theme={null}
    {
      "id": "Quote:019542f5-b3e7-1d02-0000-000000000025",
      "status": "PENDING",
      "createdAt": "2025-10-03T15:00:00Z",
      "expiresAt": "2025-10-03T15:15:00Z",
      "source": {
        "sourceType": "ACCOUNT",
        "accountId": "InternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965"
      },
      "destination": {
        "destinationType": "ACCOUNT",
        "accountId": "ExternalAccount:a12dcbd6-dced-4ec4-b756-3c3a9ea3d123"
      },
      "sendingCurrency": {
        "code": "USD",
        "name": "United States Dollar",
        "symbol": "$",
        "decimals": 2
      },
      "receivingCurrency": {
        "code": "EUR",
        "name": "Euro",
        "symbol": "€",
        "decimals": 2
      },
      "totalSendingAmount": 10000,
      "totalReceivingAmount": 9200,
      "exchangeRate": 0.92,
      "feesIncluded": 50,
      "platformFeesIncluded": 0,
      "transactionId": "Transaction:019542f5-b3e7-1d02-0000-000000000030",
      "description": "Payment for services - Invoice #1234"
    }
    ```

    <Info>
      **Same-currency transfers use this exact request.** The two currencies simply match, and
      the quote comes back with an `exchangeRate` of `1` — the fee fields are still populated.
      Add `"immediatelyExecute": true` to create and execute in this one request and skip the
      next two steps.
    </Info>

    <Info>
      **Locked currency side** determines which amount is fixed:

      * `SENDING`: Lock the sending amount (receiving amount calculated based on exchange rate)
      * `RECEIVING`: Lock the receiving amount (sending amount calculated based on exchange rate)
    </Info>

    <Tip>
      The `paymentRail` field is optional. If omitted, Grid selects a default rail for the destination. Specify a rail (e.g., `ACH`, `WIRE`, `RTP`, `FEDNOW`) when you need to control which payment network processes the transfer.
    </Tip>

    <Info>
      `remittanceInformation` is optional. Use it to send a reference that travels with the payment to the recipient (max 80 characters). This populates the ACH Addenda record, FedNow/RTP remittance information, or wire OBI field depending on the payment rail.
    </Info>

    <Info>
      `purposeOfPayment` is optional. Some destinations require it, and some rails carry it on
      the payment itself.
    </Info>

    <Info>
      Amounts are specified in the smallest unit of the currency (cents for USD, pence for GBP, etc.). For example, `12550` represents \$125.50 USD.
    </Info>
  </Step>

  <Step title="Review the quote">
    Before executing, check that:

    * The exchange rate is acceptable
    * Fees are as expected
    * The receiving amount meets requirements
    * The quote hasn't expired (check `expiresAt`)

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

    <Info>
      Skip this step by setting `immediatelyExecute` on the quote. A same-currency quote has no
      exchange rate to review, but check `feesIncluded` if your UX shows the customer what the
      transfer costs.
    </Info>
  </Step>

  <Step title="Execute the quote">
    Confirm and execute the quote to initiate the transfer:

    ```bash cURL theme={null}
    curl -X POST 'https://api.lightspark.com/grid/2025-10-13/quotes/Quote:019542f5-b3e7-1d02-0000-000000000025/execute' \
      -u "$GRID_CLIENT_ID:$GRID_CLIENT_SECRET"
    ```

    When SCA does not apply, the quote comes back with `status` `PROCESSING` and the same
    `transactionId` it carried at creation.

    <Warning>
      **Strong Customer Authentication.** Where SCA applies (currently EU customers), execute
      does **not** initiate the transfer. The quote returns with `PENDING_AUTHORIZATION` and an
      `scaChallenge`; release the transfer by authorizing it with
      `POST /quotes/{quoteId}/authorize`. Re-calling execute returns 409.
    </Warning>

    <Check>
      Once executed, the quote creates a transaction and the transfer begins processing. The `transactionId` can be used to track the payment.
    </Check>

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

  <Step title="Monitor completion">
    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:

    ```json theme={null}
    {
      "type": "OUTGOING_PAYMENT.COMPLETED",
      "data": {
        "id": "Transaction:019542f5-b3e7-1d02-0000-000000000030",
        "status": "COMPLETED",
        "type": "OUTGOING",
        "direction": "DEBIT",
        "sentAmount": {
          "amount": 10000,
          "currency": { "code": "USD", "symbol": "$", "decimals": 2 }
        },
        "receivedAmount": {
          "amount": 9200,
          "currency": { "code": "EUR", "symbol": "€", "decimals": 2 }
        },
        "exchangeRate": 0.92,
        "settledAt": "2025-10-03T15:30:00Z",
        "quoteId": "Quote:019542f5-b3e7-1d02-0000-000000000025"
      },
      "timestamp": "2025-10-03T15:31:00Z"
    }
    ```

    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.

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

### Transaction statuses

| Status       | Description                                                                                                                 |
| ------------ | --------------------------------------------------------------------------------------------------------------------------- |
| `PENDING`    | Quote created, awaiting execution                                                                                           |
| `PROCESSING` | Quote executed, transfer in progress through the payment rail                                                               |
| `COMPLETED`  | Transfer successfully completed                                                                                             |
| `FAILED`     | Transfer failed — accompanied by a `failureReason`, and a refund is initiated automatically (track via the `refund` object) |
| `EXPIRED`    | Quote wasn't executed before the expiry window                                                                              |

<Info>
  For the full state diagram including refund tracking and edge cases, see the [Transaction Lifecycle](/platform-overview/core-concepts/transaction-lifecycle) guide.
</Info>

## Checking Payment Status

Configure a webhook endpoint to receive real-time notifications when payment status changes:

```javascript theme={null}
app.post("/webhooks/grid", (req, res) => {
  const { type, data } = req.body;

  switch (type) {
    case "OUTGOING_PAYMENT.COMPLETED":
      console.log(`Payment ${data.id} completed at ${data.settledAt}`);
      // Update your database, notify customer
      break;

    case "OUTGOING_PAYMENT.FAILED":
      console.log(`Payment ${data.id} failed: ${data.failureReason}`);
      // Handle failure, notify customer — refund webhook follows
      break;

    case "OUTGOING_PAYMENT.PROCESSING":
      console.log(`Payment ${data.id} is processing`);
      // Optional: Update UI to show processing state
      break;

    case "OUTGOING_PAYMENT.REFUND_COMPLETED":
      console.log(`Payment ${data.id} refund completed`);
      // Update your records with refund details
      break;

    case "OUTGOING_PAYMENT.REFUND_FAILED":
      console.log(`Payment ${data.id} refund failed`);
      // Alert your team — may require manual resolution
      break;
  }

  res.status(200).json({ received: true });
});
```

<Tip>
  See the [Webhooks guide](/payouts-and-b2b/platform-tools/webhooks) for complete
  webhook implementation details including signature verification.
</Tip>

## Best Practices

<AccordionGroup>
  <Accordion title="Handle quote expiration gracefully">
    Quote expiration depends on the corridor (typically \~5 minutes or greater). Always check expiration before executing:

    ```javascript theme={null}
    async function executeQuoteWithCheck(quoteId) {
      const quote = await getQuote(quoteId);

      if (new Date(quote.expiresAt) < new Date()) {
        // Quote expired, create a new one
        const newQuote = await createQuote({
          source: quote.source,
          destination: quote.destination,
          lockedCurrencySide: quote.lockedCurrencySide,
          lockedCurrencyAmount: quote.lockedCurrencyAmount,
        });

        return executeQuote(newQuote.id);
      }

      return executeQuote(quoteId);
    }
    ```
  </Accordion>

  <Accordion title="Include descriptive payment references">
    Always include meaningful descriptions to help with reconciliation:

    ```javascript theme={null}
    const description = [
      `Invoice #${invoiceId}`,
      `Customer: ${customerName}`,
      `Date: ${new Date().toISOString().split("T")[0]}`,
    ].join(" | ");

    await createQuote({
      // ... other fields
      description: description,
    });
    ```

    This makes it easier to match payments in your accounting system and provides context when reviewing transactions.
  </Accordion>

  <Accordion title="Store transaction IDs in your system">
    Always save transaction and quote IDs for audit trails and support:

    ```javascript theme={null}
    const quote = await createQuote(quoteData);

    // Save to your database immediately
    await db.payments.create({
      quoteId: quote.id,
      customerId: customer.id,
      amount: quote.totalSendingAmount,
      currency: quote.sendingCurrency.code,
      status: "pending",
      createdAt: new Date(),
    });

    const execution = await executeQuote(quote.id);

    // Update with transaction ID
    await db.payments.update(
      { quoteId: quote.id },
      { transactionId: execution.transactionId, status: "processing" }
    );
    ```
  </Accordion>
</AccordionGroup>

## Next Steps

<FeatureCardGrid cols={2}>
  <FeatureCard icon="/images/icons/square-info.svg" title="Error Handling" href="/payouts-and-b2b/payment-flow/error-handling">
    Handle payment failures and error scenarios
  </FeatureCard>

  <FeatureCard icon="/images/icons/file-text.svg" title="List Transactions" href="/payouts-and-b2b/payment-flow/list-transactions">
    Query and filter transaction history
  </FeatureCard>

  <FeatureCard icon="/images/icons/receipt-check.svg" title="Reconciliation" href="/payouts-and-b2b/payment-flow/reconciliation">
    Match payments with your internal systems
  </FeatureCard>
</FeatureCardGrid>
