This guide covers integrating Stripe with PHP using Composer and the official stripe-php SDK. Set up API keys with environment variables, create customers, process payments with PaymentIntents, and handle webhooks with signature verification. All examples work with plain PHP and Laravel, using test mode with the 4242424242424242 test card and amounts in cents.
Getting Started with the Stripe API in PHP
The Stripe PHP SDK (stripe-php) is the official library for server-side Stripe integration in PHP. It supports all Stripe API features and is the most widely used Stripe SDK by total installations. This guide covers both plain PHP and Laravel integration, from Composer installation to a working payment server with webhook handling.
Prerequisites
- PHP 8.1 or later installed
- Composer for package management
- A Stripe account with test API keys
- Basic familiarity with PHP and optionally Laravel
Step-by-step guide
Install the Stripe PHP SDK with Composer
Install the Stripe PHP SDK with Composer
Use Composer to install the official Stripe library.
1# Install the Stripe PHP SDK2composer require stripe/stripe-php34# For Laravel, also install:5# composer require laravel/cashier (for subscriptions)6# Or use stripe-php directly for custom integrationsExpected result: The stripe/stripe-php package is installed and autoloaded via Composer.
Configure Stripe with API keys
Configure Stripe with API keys
Set your API key from environment variables. Never hardcode keys in source files.
1<?php2require_once 'vendor/autoload.php';34// Load from environment variable5\Stripe\Stripe::setApiKey(getenv('STRIPE_SECRET_KEY'));6\Stripe\Stripe::setApiVersion('2024-12-18.acacia');78// Verify configuration9if (!getenv('STRIPE_SECRET_KEY')) {10 die('Error: STRIPE_SECRET_KEY not set');11}1213echo 'Stripe configured with API version: ' . \Stripe\Stripe::$apiVersion;14?>Expected result: The Stripe client is configured with your test secret key from environment variables.
Create a customer and PaymentIntent
Create a customer and PaymentIntent
Build API endpoints for customer creation and payment processing.
1<?php2require_once 'vendor/autoload.php';3\Stripe\Stripe::setApiKey(getenv('STRIPE_SECRET_KEY'));45header('Content-Type: application/json');6$input = json_decode(file_get_contents('php://input'), true);78try {9 // Create a customer10 $customer = \Stripe\Customer::create([11 'email' => $input['email'],12 'name' => $input['name'] ?? null,13 'metadata' => [14 'app_user_id' => $input['userId'] ?? '',15 ],16 ]);1718 // Create a PaymentIntent19 $paymentIntent = \Stripe\PaymentIntent::create([20 'amount' => $input['amount'], // Amount in cents21 'currency' => 'usd',22 'customer' => $customer->id,23 'automatic_payment_methods' => ['enabled' => true],24 'metadata' => [25 'order_id' => $input['orderId'] ?? '',26 ],27 ]);2829 echo json_encode([30 'customerId' => $customer->id,31 'clientSecret' => $paymentIntent->client_secret,32 ]);33} catch (\Stripe\Exception\ApiErrorException $e) {34 http_response_code(400);35 echo json_encode(['error' => $e->getMessage()]);36}37?>Expected result: A customer and PaymentIntent are created. The client_secret is returned for frontend payment confirmation.
Handle webhooks with signature verification
Handle webhooks with signature verification
Verify the webhook signature using the raw POST body. This is the most critical security step.
1<?php2require_once 'vendor/autoload.php';34\Stripe\Stripe::setApiKey(getenv('STRIPE_SECRET_KEY'));5$webhookSecret = getenv('STRIPE_WEBHOOK_SECRET');67// Get the raw POST body (do NOT use json_decode first)8$payload = file_get_contents('php://input');9$sigHeader = $_SERVER['HTTP_STRIPE_SIGNATURE'] ?? '';1011try {12 $event = \Stripe\Webhook::constructEvent(13 $payload,14 $sigHeader,15 $webhookSecret16 );17} catch (\UnexpectedValueException $e) {18 http_response_code(400);19 echo 'Invalid payload';20 exit;21} catch (\Stripe\Exception\SignatureVerificationException $e) {22 http_response_code(400);23 echo 'Invalid signature';24 exit;25}2627// Handle the event28switch ($event->type) {29 case 'payment_intent.succeeded':30 $intent = $event->data->object;31 error_log("Payment succeeded: {$intent->id}");32 // Fulfill the order33 break;34 case 'payment_intent.payment_failed':35 $intent = $event->data->object;36 error_log("Payment failed: {$intent->id}");37 break;38 default:39 error_log("Unhandled event: {$event->type}");40}4142http_response_code(200);43echo json_encode(['received' => true]);44?>Expected result: Webhook signatures are verified and events are routed to appropriate handlers.
Handle Stripe errors by type
Handle Stripe errors by type
Stripe throws specific exception types for different errors. Handle each for appropriate user feedback.
1<?php2try {3 $paymentIntent = \Stripe\PaymentIntent::create([4 'amount' => 5000,5 'currency' => 'usd',6 ]);7} catch (\Stripe\Exception\CardException $e) {8 // Card was declined9 http_response_code(402);10 echo json_encode([11 'error' => $e->getMessage(),12 'code' => $e->getStripeCode(),13 'decline_code' => $e->getDeclineCode(),14 ]);15} catch (\Stripe\Exception\RateLimitException $e) {16 http_response_code(429);17 echo json_encode(['error' => 'Too many requests. Please retry.']);18} catch (\Stripe\Exception\InvalidRequestException $e) {19 http_response_code(400);20 echo json_encode(['error' => $e->getMessage()]);21} catch (\Stripe\Exception\AuthenticationException $e) {22 http_response_code(500);23 echo json_encode(['error' => 'Payment configuration error.']);24} catch (\Stripe\Exception\ApiConnectionException $e) {25 http_response_code(503);26 echo json_encode(['error' => 'Payment service unavailable.']);27} catch (\Stripe\Exception\ApiErrorException $e) {28 http_response_code(500);29 echo json_encode(['error' => 'Unexpected payment error.']);30}31?>Expected result: Each Stripe error type returns an appropriate HTTP status code and user-friendly message.
Laravel controller integration
Laravel controller integration
If you use Laravel, integrate Stripe through a controller with proper request handling.
1<?php2// app/Http/Controllers/StripeController.php3namespace App\Http\Controllers;45use Illuminate\Http\Request;6use Stripe\Stripe;7use Stripe\PaymentIntent;8use Stripe\Customer;9use Stripe\Webhook;1011class StripeController extends Controller12{13 public function __construct()14 {15 Stripe::setApiKey(env('STRIPE_SECRET_KEY'));16 }1718 public function createPaymentIntent(Request $request)19 {20 try {21 $intent = PaymentIntent::create([22 'amount' => $request->input('amount'),23 'currency' => 'usd',24 'automatic_payment_methods' => ['enabled' => true],25 ]);26 return response()->json(['clientSecret' => $intent->client_secret]);27 } catch (\Stripe\Exception\ApiErrorException $e) {28 return response()->json(['error' => $e->getMessage()], 400);29 }30 }3132 public function webhook(Request $request)33 {34 $payload = $request->getContent();35 $sig = $request->header('Stripe-Signature');36 try {37 $event = Webhook::constructEvent(38 $payload, $sig, env('STRIPE_WEBHOOK_SECRET')39 );40 } catch (\Exception $e) {41 return response('Invalid', 400);42 }4344 if ($event->type === 'payment_intent.succeeded') {45 \Log::info('Payment: ' . $event->data->object->id);46 }4748 return response()->json(['received' => true]);49 }50}51?>Expected result: Laravel controller handles PaymentIntent creation and webhook verification using the Stripe SDK.
Complete working example
1<?php2require_once 'vendor/autoload.php';34\Stripe\Stripe::setApiKey(getenv('STRIPE_SECRET_KEY'));5\Stripe\Stripe::setApiVersion('2024-12-18.acacia');67header('Content-Type: application/json');89$uri = $_SERVER['REQUEST_URI'];10$method = $_SERVER['REQUEST_METHOD'];1112if ($uri === '/api/config' && $method === 'GET') {13 echo json_encode(['publishableKey' => getenv('STRIPE_PUBLISHABLE_KEY')]);14 exit;15}1617if ($uri === '/api/create-payment-intent' && $method === 'POST') {18 $input = json_decode(file_get_contents('php://input'), true);19 try {20 $intent = \Stripe\PaymentIntent::create([21 'amount' => $input['amount'],22 'currency' => 'usd',23 'customer' => $input['customerId'] ?? null,24 'automatic_payment_methods' => ['enabled' => true],25 ]);26 echo json_encode(['clientSecret' => $intent->client_secret]);27 } catch (\Stripe\Exception\ApiErrorException $e) {28 http_response_code(400);29 echo json_encode(['error' => $e->getMessage()]);30 }31 exit;32}3334if ($uri === '/api/customers' && $method === 'POST') {35 $input = json_decode(file_get_contents('php://input'), true);36 try {37 $customer = \Stripe\Customer::create([38 'email' => $input['email'],39 'name' => $input['name'] ?? null,40 ]);41 echo json_encode(['customerId' => $customer->id]);42 } catch (\Stripe\Exception\ApiErrorException $e) {43 http_response_code(400);44 echo json_encode(['error' => $e->getMessage()]);45 }46 exit;47}4849if ($uri === '/webhook' && $method === 'POST') {50 $payload = file_get_contents('php://input');51 $sig = $_SERVER['HTTP_STRIPE_SIGNATURE'] ?? '';52 try {53 $event = \Stripe\Webhook::constructEvent(54 $payload, $sig, getenv('STRIPE_WEBHOOK_SECRET')55 );56 } catch (\Exception $e) {57 http_response_code(400);58 echo 'Webhook error: ' . $e->getMessage();59 exit;60 }61 switch ($event->type) {62 case 'payment_intent.succeeded':63 error_log('Payment: ' . $event->data->object->id);64 break;65 case 'payment_intent.payment_failed':66 error_log('Failed: ' . $event->data->object->id);67 break;68 }69 echo json_encode(['received' => true]);70 exit;71}7273http_response_code(404);74echo json_encode(['error' => 'Not found']);75?>Common mistakes when using Stripe API with PHP
Why it's a problem: Using json_decode on the POST body before webhook signature verification
How to avoid: Use file_get_contents('php://input') to get raw bytes for Webhook::constructEvent. JSON parsing alters the payload and breaks verification.
Why it's a problem: Hardcoding API keys in PHP files
How to avoid: Use getenv('STRIPE_SECRET_KEY') or Laravel's env() function. Store keys in .env files excluded from version control.
Why it's a problem: Not catching specific Stripe exception types
How to avoid: Catch CardException, RateLimitException, InvalidRequestException separately for appropriate error responses.
Why it's a problem: Not disabling CSRF protection for the webhook route in Laravel
How to avoid: Add the webhook URL to the $except array in VerifyCsrfToken middleware, or use $request->getContent() for raw body access.
Best practices
- Install stripe-php via Composer — never download the SDK manually
- Pin the API version with Stripe::setApiVersion() to prevent breaking changes
- Use environment variables for all API keys, never hardcode them in PHP files
- Use file_get_contents('php://input') for raw body in webhook handlers
- Handle all Stripe exception types with appropriate HTTP status codes
- Use test card 4242424242424242 with any future expiry and any CVC
- In Laravel, exclude the webhook route from CSRF verification
- Test webhooks locally with the Stripe CLI: stripe listen --forward-to localhost:8000/webhook
Still stuck?
Copy one of these prompts to get a personalized, step-by-step explanation.
How do I integrate Stripe with PHP? I need to install the SDK via Composer, create PaymentIntents, handle webhooks with signature verification, and manage errors. Show me code for both plain PHP and Laravel.
Build a Stripe payment server in PHP. I need customer creation, PaymentIntent creation, webhook handling with signature verification, and error handling. Show me both plain PHP and Laravel controller versions.
Frequently asked questions
Which PHP version does the Stripe SDK require?
The stripe-php v13+ requires PHP 8.1 or later. Older versions of the SDK support PHP 7.4+ but are no longer actively maintained.
Should I use Laravel Cashier or stripe-php directly?
Use Laravel Cashier for subscription billing — it handles subscription lifecycle, invoicing, and webhooks automatically. Use stripe-php directly for one-time payments, Connect, or custom payment flows.
How do I access the Stripe Dashboard webhook secret in Laravel?
Add STRIPE_WEBHOOK_SECRET=whsec_... to your .env file and access it with env('STRIPE_WEBHOOK_SECRET') in your controller.
Can I use Stripe with WordPress?
Yes. Many WordPress plugins (WooCommerce Stripe Gateway, WP Simple Pay) provide Stripe integration. For custom WordPress development, require stripe-php via Composer and use it in your plugin or theme.
Can RapidDev help build custom PHP Stripe integrations?
Yes. RapidDev builds production-ready Stripe integrations in PHP, Laravel, and WordPress including payments, subscriptions, and marketplace solutions with Connect.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation