Skip to main content
Important IDs to store:
  • checkout_session_id - Extract from redirect URL (e.g., fcs_01kbjcmbt5mhsmaggfqc1a4rns)
  • This ID is required for creating refunds
You can also retrieve the checkout session to get additional details:
GET /v1/checkout/sessions/{checkout_session_id}
Response includes:
  • payment_intent_id - Flex’s internal payment ID
  • latest_charge - Charge ID
  • status - Should be complete for refunds

Primary Refund Endpoint

POST /v1/checkout/sessions//refund

This is the main refund endpoint for processing refunds on completed checkout sessions. Base URL: https://api.withflex.com (or your custom domain) Authentication:
Authorization: Bearer YOUR_API_KEY
Content-Type: application/json

Request Format

Option 1: Line Item-Based Refund (Granular Control)

Refund specific products/prices with custom amounts:
{
  "checkout_session": {
    "line_items": [
      {
        "product": "fprod_01k3m6j3zqcvy6pbj1a65sszpw",
        "amount_to_refund": 2000
      },
      {
        "product": "fprod_01k3m6r0qj2bpc25q6h56r3jar",
        "amount_to_refund": 1500
      }
    ],
    "amount_tax": 350,
    "amount_shipping": 500,
    "amount_discount": 200,
    "refund_metadata": {
      "OrderNo": "ORDER123",
      "Source": "Customer_Request",
      "Reason": "Damaged_Product"
    }
  }
}
Alternative: Use price instead of product
{
  "checkout_session": {
    "line_items": [
      {
        "price": "fprice_01k3m6j3zqcvy6pbj1a65sszpw",
        "amount_to_refund": 2000
      }
    ],
    "refund_metadata": {
      "reason": "customer_request"
    }
  }
}

Option 2: Simple Amount-Based Refund

Refund a specific total amount without line item breakdown:
{
  "checkout_session": {
    "amount": 5000,
    "refund_metadata": {
      "reason": "customer_not_satisfied",
      "order_id": "12345"
    }
  }
}

Option 3: Full Refund

Refund the entire checkout session amount:
{
  "checkout_session": {
    "refund_metadata": {
      "reason": "order_cancelled"
    }
  }
}
Or simply:
{
  "checkout_session": {}
}

Request Parameters

FieldTypeRequiredDescription
line_itemsarrayNoSpecific line items to refund with amounts
line_items[].productstringOne ofProduct ID to refund (e.g., fprod_xxx)
line_items[].pricestringproduct/pricePrice ID to refund (e.g., fprice_xxx)
line_items[].amount_to_refundintegerNoAmount in cents for this item (omit for full item refund)
amountintegerNoTotal amount to refund in cents (alternative to line_items)
amount_taxintegerNoTax amount to refund in cents
amount_shippingintegerNoShipping amount to refund in cents
amount_discountintegerNoDiscount amount to refund in cents
refund_metadataobjectNoKey-value pairs for tracking (order ID, reason, etc.)

Success Response

Status Code: 200 OK
{
  "checkout_session": {
    "checkout_session_id": "fcs_01kbjcmbt5mhsmaggfqc1a4rns",
    "status": "complete",
    "amount_total": 10000,
    "amount_subtotal": 9000,
    "currency": "usd",
    "customer": {
      "customer_id": "fcus_xxx",
      "email": "customer@example.com",
      "name": "John Doe"
    },
    "payment_intent": {
      "payment_intent_id": "fpi_xxx",
      "amount": 10000,
      "amount_received": 10000,
      "status": "succeeded"
    },
    "line_items": [...],
    "refunds": [
      {
        "refund_id": "fref_01kbjdxyz123",
        "payment_intent_id": "fpi_xxx",
        "amount": 3500,
        "status": "pending",
        "reason": null,
        "created_at": "2024-12-04T10:30:00Z",
        "test_mode": false,
        "metadata": {
          "OrderNo": "ORDER123",
          "Source": "Customer_Request"
        },
        "reference_id": null,
        "reference_type": "acquirer_reference_number",
        "reference_status": "pending",
        "items": [
          {
            "amount_refunded": 3500,
            "payment_intent": "fpi_xxx",
            "reference_id": null,
            "reference_type": "acquirer_reference_number",
            "reference_status": "pending"
          }
        ]
      }
    ]
  }
}
Key Response Fields:
  • refunds[].refund_id - Store this for tracking refund status
  • refunds[].status - Current refund status (pending, succeeded, failed)
  • refunds[].reference_id - Acquirer Reference Number (ARN) for bank tracking (populated later)
  • refunds[].reference_status - Status of reference number (pending, available, unavailable)

Error Responses

Checkout Session Not Complete (422 Unprocessable Entity)

{
  "error": {
    "type": "validation_error",
    "message": "Checkout session must be complete",
    "errors": [
      {
        "loc": ["status"],
        "msg": "Checkout session must be complete",
        "type": "invalid_request_error"
      }
    ]
  }
}
Cause: Checkout session status is not complete

Checkout Session Not Found (404 Not Found)

{
  "error": {
    "type": "not_found",
    "message": "checkout session does not exist"
  }
}
Cause: Invalid checkout_session_id or doesn’t belong to your account

Unauthorized (401 Unauthorized)

{
  "error": {
    "type": "unauthorized",
    "message": "Invalid API key"
  }
}
Cause: Missing or invalid Authorization header

Refund Statuses

StatusDescription
pendingRefund initiated, processing in progress
requires_actionAdditional action required (rare)
succeededRefund completed, funds returned to customer
failedRefund failed (usually insufficient funds in connected account)
canceledRefund was canceled
Typical Timeline:
  • Refund status updates to succeeded within minutes to hours
  • Funds appear in customer’s account within 5-10 business days (varies by bank)

Reference Numbers for Tracking

Flex provides reference numbers that can be used to track refunds with banks and payment networks.

Reference Types

TypeDescription
acquirer_reference_numberARN - Most common, used for tracking with issuing banks
system_trace_audit_numberAlternative tracking number for payment networks
retrieval_reference_numberUsed for dispute resolution and chargebacks

Reference Status

StatusDescription
pendingReference number not yet available from payment network
availableReference number ready, can be provided to customer/bank
unavailablePayment network doesn’t provide this reference type
Note: Reference IDs typically become available within hours after the refund is created. Learn More: Stripe Reference Numbers Guide

Complete Workflow Examples

Example 1: Full Refund

Step 1: Customer completes payment You receive redirect after successful payment:
https://yoursite.com/success?session_id=fcs_01kbjcmbt5mhsmaggfqc1a4rns
Step 2: Customer requests full refund
curl -X POST https://api.withflex.com/v1/checkout/sessions/fcs_01kbjcmbt5mhsmaggfqc1a4rns/refund \
  -H "Authorization: Bearer sk_live_xxx" \
  -H "Content-Type: application/json" \
  -d '{
    "checkout_session": {
      "refund_metadata": {
        "reason": "customer_cancelled_order",
        "order_id": "ORD-12345",
        "requested_by": "customer_service"
      }
    }
  }'
Step 3: Response
{
  "checkout_session": {
    "checkout_session_id": "fcs_01kbjcmbt5mhsmaggfqc1a4rns",
    "status": "complete",
    "amount_total": 15000,
    "refunds": [
      {
        "refund_id": "fref_01kbjdxyz123",
        "amount": 15000,
        "status": "pending",
        "created_at": "2024-12-04T10:30:00Z",
        "metadata": {
          "reason": "customer_cancelled_order",
          "order_id": "ORD-12345"
        }
      }
    ]
  }
}
Step 4: Track refund status (optional)
curl -X GET https://api.withflex.com/v1/refunds/fref_01kbjdxyz123 \
  -H "Authorization: Bearer sk_live_xxx"

Example 2: Partial Refund by Line Items

Scenario: Customer returns 1 of 3 items purchased Request:
curl -X POST https://api.withflex.com/v1/checkout/sessions/fcs_01kbjcmbt5mhsmaggfqc1a4rns/refund \
  -H "Authorization: Bearer sk_live_xxx" \
  -H "Content-Type: application/json" \
  -d '{
    "checkout_session": {
      "line_items": [
        {
          "product": "fprod_yoga_mat",
          "amount_to_refund": 3500
        }
      ],
      "amount_tax": 280,
      "amount_shipping": 0,
      "refund_metadata": {
        "reason": "item_returned",
        "rma_number": "RMA-2024-1234",
        "returned_items": "Yoga Mat (Blue)"
      }
    }
  }'
Explanation:
  • Refunding only the yoga mat: $35.00
  • Including proportional tax: $2.80
  • Not refunding shipping (customer kept other items)
  • Total refund: $37.80

Example 3: Partial Refund by Amount

Scenario: Price adjustment without item-level detail Request:
curl -X POST https://api.withflex.com/v1/checkout/sessions/fcs_01kbjcmbt5mhsmaggfqc1a4rns/refund \
  -H "Authorization: Bearer sk_live_xxx" \
  -H "Content-Type: application/json" \
  -d '{
    "checkout_session": {
      "amount": 2000,
      "refund_metadata": {
        "reason": "price_match_guarantee",
        "original_amount": 15000,
        "adjusted_amount": 13000,
        "competitor": "CompetitorStore"
      }
    }
  }'
Explanation:
  • Refunding $20.00 difference due to price match
  • No line item breakdown needed
  • Metadata tracks the reason

Example 4: Multiple Partial Refunds

You can create multiple refunds for the same checkout session until the full amount is refunded. First refund: $50
curl -X POST https://api.withflex.com/v1/checkout/sessions/fcs_xxx/refund \
  -H "Authorization: Bearer sk_live_xxx" \
  -H "Content-Type: application/json" \
  -d '{"checkout_session": {"amount": 5000}}'
Second refund: $30
curl -X POST https://api.withflex.com/v1/checkout/sessions/fcs_xxx/refund \
  -H "Authorization: Bearer sk_live_xxx" \
  -H "Content-Type: application/json" \
  -d '{"checkout_session": {"amount": 3000}}'
Important: Track the total refunded amount to avoid over-refunding.

Secondary Refund API

Tracking and Listing Refunds

After creating a refund via the checkout session endpoint, use the secondary refund API to track status:

GET /v1/refunds/

curl -X GET https://api.withflex.com/v1/refunds/fref_01kbjdxyz123 \
  -H "Authorization: Bearer sk_live_xxx"
Response:
{
  "refund": {
    "refund_id": "fref_01kbjdxyz123",
    "payment_intent_id": "fpi_xxx",
    "amount": 5000,
    "status": "succeeded",
    "reason": null,
    "created_at": "2024-12-04T10:30:00Z",
    "test_mode": false,
    "metadata": {
      "order_id": "12345"
    },
    "reference_id": "ARN8493274932",
    "reference_type": "acquirer_reference_number",
    "reference_status": "available",
    "items": [...]
  }
}

GET /v1/refunds (List Refunds)

Query Parameters:
  • payment_intent - Filter by payment intent ID
  • checkout_session - Filter by checkout session ID
  • status - Filter by status
Examples:
# All refunds
curl -X GET https://api.withflex.com/v1/refunds \
  -H "Authorization: Bearer sk_live_xxx"

# Refunds for specific checkout session
curl -X GET https://api.withflex.com/v1/refunds?checkout_session=fcs_01kbjcmbt5mhsmaggfqc1a4rns \
  -H "Authorization: Bearer sk_live_xxx"

# Succeeded refunds only
curl -X GET https://api.withflex.com/v1/refunds?status=succeeded \
  -H "Authorization: Bearer sk_live_xxx"
Response:
{
  "refunds": [
    {
      "refund_id": "fref_01kbjdxyz123",
      "payment_intent_id": "fpi_xxx",
      "amount": 5000,
      "status": "succeeded",
      "created_at": "2024-12-04T10:30:00Z",
      ...
    },
    {
      "refund_id": "fref_01kbjdabc456",
      "payment_intent_id": "fpi_yyy",
      "amount": 3000,
      "status": "pending",
      "created_at": "2024-12-03T15:20:00Z",
      ...
    }
  ]
}

PATCH /v1/refunds/ (Update Refund Metadata)

Update refund metadata or reason (does not change amount or status):
curl -X PATCH https://api.withflex.com/v1/refunds/fref_01kbjdxyz123 \
  -H "Authorization: Bearer sk_live_xxx" \
  -H "Content-Type: application/json" \
  -d '{
    "refund": {
      "reason": "fraudulent",
      "metadata": {
        "updated_by": "admin",
        "investigation_id": "INV-2024-001"
      }
    }
  }'

Implementation Details

Automatic Handling by Flex

Flex automatically handles: Reverse Transfer - Funds are clawed back from the connected account (partner) ✅ Application Fee Retention - Flex keeps the platform fee (not refunded) ✅ Webhook Processing - Status updates via Stripe webhooks ✅ Charge Tracking - Records which charges were refunded in refund_charge table ✅ Reference Number Retrieval - ARN and other tracking numbers populated when available

Partial Refund Behavior

  • Can refund any amount up to the original charge amount
  • Multiple partial refunds allowed until full amount refunded
  • Track amount_received vs total refunded to calculate remaining refundable amount

Tax, Shipping, and Discount Refunds

When refunding line items, you can specify component amounts:
{
  "checkout_session": {
    "line_items": [
      {
        "product": "fprod_xxx",
        "amount_to_refund": 5000
      }
    ],
    "amount_tax": 400,        // Proportional tax for refunded items
    "amount_shipping": 500,    // Shipping refund
    "amount_discount": 250     // Discount adjustment
  }
}
Best Practice: Calculate proportional tax when refunding partial line items.

Best Practices

1. Store Checkout Session ID

Always save the checkout_session_id after payment completion:
// After payment redirect
const urlParams = new URLSearchParams(window.location.search);
const sessionId = urlParams.get('session_id');

// Store in your database
await db.orders.update({
  orderId: orderId,
  flexCheckoutSessionId: sessionId
});

2. Verify Checkout Session Status

Before attempting refund, confirm the session is complete:
const session = await fetch(
  `https://api.withflex.com/v1/checkout/sessions/${sessionId}`,
  {
    headers: {
      'Authorization': `Bearer ${apiKey}`
    }
  }
).then(r => r.json());

if (session.checkout_session.status !== 'complete') {
  throw new Error('Cannot refund incomplete checkout session');
}

3. Use Metadata for Tracking

Always include metadata to track refund context:
{
  "checkout_session": {
    "amount": 5000,
    "refund_metadata": {
      "order_id": "ORD-12345",
      "reason": "customer_request",
      "requested_by": "customer_service_agent_42",
      "ticket_id": "TICKET-789",
      "timestamp": "2024-12-04T10:30:00Z"
    }
  }
}

4. Handle Refund Webhooks

Subscribe to Flex webhooks for automatic refund status updates: Event Types:
  • refund.created - Refund initiated
  • refund.succeeded - Refund completed successfully
  • refund.failed - Refund failed
Webhook Payload Example:
{
  "type": "refund.succeeded",
  "data": {
    "object": {
      "refund_id": "fref_xxx",
      "payment_intent_id": "fpi_xxx",
      "amount": 5000,
      "status": "succeeded",
      "reference_id": "ARN123456789"
    }
  }
}

5. Track Remaining Refundable Amount

For multiple partial refunds, track the remaining amount:
const session = await getCheckoutSession(sessionId);
const totalCharged = session.amount_total;
const totalRefunded = session.refunds.reduce((sum, r) => sum + r.amount, 0);
const remainingRefundable = totalCharged - totalRefunded;

console.log(`Can still refund: $${remainingRefundable / 100}`);

6. Save Reference Numbers

Store ARNs for customer support inquiries:
const refund = await getRefund(refundId);

if (refund.reference_status === 'available') {
  await db.refunds.update({
    refundId: refundId,
    arn: refund.reference_id,
    referenceType: refund.reference_type
  });

  // Provide to customer for bank tracking
  console.log(`ARN: ${refund.reference_id}`);
}

7. Handle Errors Gracefully

try {
  const response = await fetch(refundUrl, {
    method: 'POST',
    headers: { 'Authorization': `Bearer ${apiKey}` },
    body: JSON.stringify(refundRequest)
  });

  if (!response.ok) {
    const error = await response.json();

    if (error.error.type === 'validation_error') {
      console.error('Validation error:', error.error.message);
      // Handle validation errors
    } else if (response.status === 404) {
      console.error('Checkout session not found');
      // Handle not found
    } else {
      console.error('Unexpected error:', error);
      // Handle other errors
    }
  }

  const result = await response.json();
  console.log('Refund created:', result.checkout_session.refunds[0].refund_id);

} catch (error) {
  console.error('Network error:', error);
  // Handle network errors
}

Testing

Test Mode

Use test API keys (starting with sk_test_) to test refunds without affecting real money:
curl -X POST https://api.withflex.com/v1/checkout/sessions/fcs_test_xxx/refund \
  -H "Authorization: Bearer sk_test_xxx" \
  -H "Content-Type: application/json" \
  -d '{"checkout_session": {}}'
Test Mode Behavior:
  • Refunds complete instantly with status: "succeeded"
  • No actual money movement
  • Reference IDs still generated for testing
  • Full webhook flow works

Test Credentials

For testing payments → refunds flow: Email: miguel@homefit.com Password: password Create test API keys at: http://localhost:3001/partners/dashboard/developers

Troubleshooting

Problem: “Checkout session must be complete”

Cause: Trying to refund a checkout session that hasn’t been paid or is in progress Solution:
  1. Check session status: GET /v1/checkout/sessions/{id}
  2. Ensure status === "complete"
  3. Wait for payment to finish if still processing

Problem: “checkout session does not exist”

Cause: Invalid checkout session ID or doesn’t belong to your account Solution:
  1. Verify the checkout_session_id is correct
  2. Ensure you’re using the right API key (test vs. production)
  3. Check that the session belongs to your partner account

Problem: Refund shows “pending” for too long

Cause: Stripe processing delay or connected account issues Solution:
  1. Wait up to 24 hours for normal processing
  2. Check Stripe dashboard for errors
  3. Verify connected account has sufficient balance
  4. Contact support if still pending after 24 hours

Problem: “Failed to create refund”

Cause: Stripe API error (usually insufficient funds in connected account) Solution:
  1. Check connected account balance in Stripe dashboard
  2. Ensure partner account has funds to cover refund
  3. Review Stripe logs for specific error details
  4. Contact Flex support with checkout_session_id

Problem: Reference ID not available

Cause: Payment network hasn’t provided ARN yet Solution:
  1. Reference IDs typically appear within hours
  2. Check reference_status - if pending, wait
  3. If unavailable, payment network doesn’t provide this type
  4. Poll /v1/refunds/{refund_id} periodically for updates

API Endpoint Summary

EndpointMethodPurpose
/v1/checkout/sessions/{id}/refundPOSTPrimary - Create refund for checkout session
/v1/refundsGETList all refunds with filters
/v1/refunds/{refund_id}GETGet single refund details
/v1/refunds/{refund_id}PATCHUpdate refund metadata
/v1/checkout/sessions/{id}GETGet checkout session details

Common Use Cases

Use Case 1: Customer Service Full Refund

async function processFullRefund(checkoutSessionId, reason) {
  const response = await fetch(
    `https://api.withflex.com/v1/checkout/sessions/${checkoutSessionId}/refund`,
    {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${process.env.FLEX_API_KEY}`,
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({
        checkout_session: {
          refund_metadata: {
            reason: reason,
            processed_by: 'customer_service',
            timestamp: new Date().toISOString()
          }
        }
      })
    }
  );

  const result = await response.json();
  const refund = result.checkout_session.refunds[0];

  console.log(`Refund created: ${refund.refund_id}`);
  return refund;
}

Use Case 2: Return Processing with Line Items

async function processReturn(checkoutSessionId, returnedItems) {
  // returnedItems = [{ productId, amount }, ...]

  const lineItems = returnedItems.map(item => ({
    product: item.productId,
    amount_to_refund: item.amount
  }));

  const response = await fetch(
    `https://api.withflex.com/v1/checkout/sessions/${checkoutSessionId}/refund`,
    {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${process.env.FLEX_API_KEY}`,
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({
        checkout_session: {
          line_items: lineItems,
          amount_tax: calculateProportionalTax(returnedItems),
          refund_metadata: {
            reason: 'items_returned',
            rma_number: generateRmaNumber()
          }
        }
      })
    }
  );

  return await response.json();
}

Use Case 3: Price Adjustment

async function applyPriceAdjustment(checkoutSessionId, adjustmentAmount, reason) {
  const response = await fetch(
    `https://api.withflex.com/v1/checkout/sessions/${checkoutSessionId}/refund`,
    {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${process.env.FLEX_API_KEY}`,
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({
        checkout_session: {
          amount: adjustmentAmount,
          refund_metadata: {
            reason: 'price_adjustment',
            adjustment_type: reason,
            original_ticket: 'TICKET-123'
          }
        }
      })
    }
  );

  return await response.json();
}

Additional Resources


Support

For issues or questions about refunds:
  1. Check this guide for common solutions
  2. Review Stripe dashboard for payment details
  3. Contact Flex support with:
    • checkout_session_id
    • refund_id (if refund was created)
    • Error messages
    • Timestamp of the issue