Using Redirects Instead of Webhooks

When creating checkout sessions with Flex, you have two options for handling successful payments:
  1. Webhooks - Flex sends webhook events to your server when payments complete
  2. Redirects - Users are redirected to your success URL, and you fetch the checkout session details
This guide covers how to use the redirect approach, which can be simpler to implement and test.

Overview

Instead of setting up webhook endpoints, you can:
  1. Create a checkout session with success_url and cancel_url parameters
  2. Redirect users to the checkout page
  3. When payment completes, users are redirected to your success_url with the checkout session ID as a query parameter
  4. Fetch the checkout session details from your success page using the Flex API

Creating a Checkout Session with Redirect URLs

When creating a checkout session, specify the URLs where users should be redirected:
curl -X POST https://api.withflex.com/v1/checkout/sessions \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "success_url": "https://yoursite.com/success?session_id={CHECKOUT_SESSION_ID}",
    "cancel_url": "https://yoursite.com/cancel",
    "line_items": [{
      "price_id": "price_123",
      "quantity": 1
    }],
    "mode": "payment"
  }'
Important: Include {CHECKOUT_SESSION_ID} in your success URL.
Flex will replace this with the actual checkout session ID when redirecting the user.

Handling the Success Redirect

When a payment is successful, the user will be redirected to your success URL with the checkout session ID as a query parameter. On your success page, extract this ID and fetch the full checkout session details.

Example: Node.js/Express

app.get('/success', async (req, res) => {
  const sessionId = req.query.session_id;
  
  if (!sessionId) {
    return res.status(400).send('Missing session_id parameter');
  }

  try {
    // Fetch the checkout session details
    const response = await fetch(`https://api.withflex.com/v1/checkout/sessions/${sessionId}`, {
      headers: {
        'Authorization': `Bearer ${YOUR_API_KEY}`,
        'Content-Type': 'application/json'
      }
    });

    const { checkout_session } = await response.json();
    
    // Check if payment was successful
    if (checkout_session.status === 'complete') {
      // Payment successful! Handle accordingly
      console.log('Payment completed:', checkout_session.id);
      console.log('Customer email:', checkout_session.customer_details?.email);
      console.log('Amount paid:', checkout_session.amount_total);
      
      // Update your database, send confirmation email, etc.
      await fulfillOrder(checkout_session);
      
      res.render('success', { session: checkout_session });
    } else {
      res.status(400).render('error', { 
        message: 'Payment not completed' 
      });
    }
  } catch (error) {
    console.error('Error fetching checkout session:', error);
    res.status(500).render('error', { 
      message: 'Failed to verify payment' 
    });
  }
});

Example: Python/Django

from django.http import JsonResponse, HttpResponse
from django.shortcuts import render
import requests

def success_view(request):
    session_id = request.GET.get('session_id')
    
    if not session_id:
        return HttpResponse('Missing session_id parameter', status=400)
    
    try:
        # Fetch checkout session from Flex API
        response = requests.get(
            f'https://api.withflex.com/v1/checkout/sessions/{session_id}',
            headers={
                'Authorization': f'Bearer {YOUR_API_KEY}',
                'Content-Type': 'application/json'
            }
        )
        response.raise_for_status()
        
        data = response.json()
        checkout_session = data['checkout_session']
        
        if checkout_session['status'] == 'complete':
            # Payment successful - fulfill order
            fulfill_order(checkout_session)
            
            return render(request, 'success.html', {
                'session': checkout_session
            })
        else:
            return HttpResponse('Payment not completed', status=400)
            
    except requests.exceptions.RequestException as e:
        print(f'Error fetching checkout session: {e}')
        return HttpResponse('Failed to verify payment', status=500)

Example: PHP

<?php
function handleSuccess() {
    $sessionId = $_GET['session_id'] ?? null;
    
    if (!$sessionId) {
        http_response_code(400);
        echo 'Missing session_id parameter';
        return;
    }
    
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, "https://api.withflex.com/v1/checkout/sessions/$sessionId");
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_HTTPHEADER, [
        'Authorization: Bearer ' . YOUR_API_KEY,
        'Content-Type: application/json'
    ]);
    
    $response = curl_exec($ch);
    $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    curl_close($ch);
    
    if ($httpCode === 200) {
        $data = json_decode($response, true);
        $checkoutSession = $data['checkout_session'];
        
        if ($checkoutSession['status'] === 'complete') {
            // Payment successful
            fulfillOrder($checkoutSession);
            
            // Render success page
            include 'success.php';
        } else {
            http_response_code(400);
            echo 'Payment not completed';
        }
    } else {
        http_response_code(500);
        echo 'Failed to verify payment';
    }
}

handleSuccess();
?>

API Endpoint Details

GET /v1/checkout/sessions/

Retrieves a checkout session by ID. URL: https://api.withflex.com/v1/checkout/sessions/{CHECKOUT_SESSION_ID} Method: GET Headers:
  • Authorization: Bearer YOUR_API_KEY
  • Content-Type: application/json
Response:
{
  "checkout_session": {
    "id": "cs_1234567890",
    "status": "complete",
    "amount_total": 2000,
    "currency": "usd",
    "customer_details": {
      "email": "customer@example.com",
      "name": "John Doe"
    },
    "success_url": "https://yoursite.com/success?session_id=cs_1234567890",
    "cancel_url": "https://yoursite.com/cancel",
    "line_items": [...],
    "created": 1647887400,
    "expires_at": 1647973800
  }
}
Key Fields:
  • status: Payment status (open, complete, expired)
  • amount_total: Total amount in cents
  • customer_details: Customer information
  • line_items: Items purchased

Handling Cancellations

Users who cancel the checkout process are redirected to your cancel_url. No checkout session ID is provided in this case.
app.get('/cancel', (req, res) => {
  res.render('cancelled', {
    message: 'Your payment was cancelled. You can try again.'
  });
});

Security Considerations

1. Verify the Checkout Session

Always fetch the checkout session from the Flex API to verify its authenticity. Never trust data passed via URL parameters alone.

2. Check Payment Status

Ensure the checkout session status is complete before fulfilling orders:
if (checkout_session.status !== 'complete') {
  throw new Error('Payment not completed');
}

3. Prevent Duplicate Processing

Implement idempotency to prevent processing the same order multiple times:
// Check if order was already processed
const existingOrder = await Order.findOne({ 
  checkout_session_id: sessionId 
});

if (existingOrder) {
  return res.redirect('/already-processed');
}

// Process new order
await createOrder(checkout_session);

4. Handle Expired Sessions

Checkout sessions expire after 24 hours. Handle expired sessions gracefully:
if (checkout_session.status === 'expired') {
  return res.render('expired', {
    message: 'This checkout session has expired. Please start over.'
  });
}

Testing

Test Mode

Use test API keys to create test checkout sessions. Test payments will use Stripe’s test card numbers.

Local Development

For local testing, use tools like ngrok to create public URLs:
# Start your local server
npm start  # or your preferred method

# In another terminal, expose your local server
ngrok http 3000

# Use the ngrok URLs in your checkout session
{
  "success_url": "https://abc123.ngrok.io/success?session_id={CHECKOUT_SESSION_ID}",
  "cancel_url": "https://abc123.ngrok.io/cancel"
}

Benefits of Using Redirects

Pros

  • Simpler setup: No need to configure webhook endpoints
  • Easier testing: Immediate feedback during development
  • More reliable: No risk of missed webhook deliveries
  • Better user experience: Instant confirmation on your site

Cons

  • User-dependent: Requires user to complete the redirect flow
  • No background processing: Can’t handle events when user closes browser
  • Limited to successful payments: Doesn’t handle failed payments after completion

When to Use Redirects vs Webhooks

Use Redirects When

  • You want simple, quick implementation
  • Your fulfillment process can happen synchronously
  • You primarily need to handle successful payments
  • You want to show immediate confirmation to users

Use Webhooks When

  • You need to handle all payment events (failed, disputed, etc.)
  • You have complex background processing requirements
  • You need to handle payments that complete after the user leaves your site
  • You want to decouple payment processing from user sessions

Combining Both Approaches

You can use both redirects and webhooks together:
  • Use redirects for immediate user feedback and basic order fulfillment
  • Use webhooks for comprehensive event handling and background processing
This provides the best user experience while ensuring robust payment processing.

Need help? If you have questions about implementing redirects or need assistance with your integration, please reach out to our support team.