Using Redirects Instead of Webhooks
When creating checkout sessions with Flex, you have two options for handling successful payments:
- Webhooks - Flex sends webhook events to your server when payments complete
- 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:
- Create a checkout session with
success_url
and cancel_url
parameters
- Redirect users to the checkout page
- When payment completes, users are redirected to your
success_url
with the checkout session ID as a query parameter
- 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"
}
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.