Skip to main content
RapidDev - Software Development Agency
stripe-guide

How to use Stripe API with PHP

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.

What you'll learn

  • How to install the Stripe PHP SDK via Composer
  • How to create customers and process payments with PaymentIntents
  • How to handle webhooks with signature verification in PHP
  • How to integrate Stripe with Laravel controllers
Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Beginner6 min read15 minutesStripe PHP SDK v13+, PHP 8.1+, Laravel 10+March 2026RapidDev Engineering Team
TL;DR

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

1

Install the Stripe PHP SDK with Composer

Use Composer to install the official Stripe library.

typescript
1# Install the Stripe PHP SDK
2composer require stripe/stripe-php
3
4# For Laravel, also install:
5# composer require laravel/cashier (for subscriptions)
6# Or use stripe-php directly for custom integrations

Expected result: The stripe/stripe-php package is installed and autoloaded via Composer.

2

Configure Stripe with API keys

Set your API key from environment variables. Never hardcode keys in source files.

typescript
1<?php
2require_once 'vendor/autoload.php';
3
4// Load from environment variable
5\Stripe\Stripe::setApiKey(getenv('STRIPE_SECRET_KEY'));
6\Stripe\Stripe::setApiVersion('2024-12-18.acacia');
7
8// Verify configuration
9if (!getenv('STRIPE_SECRET_KEY')) {
10 die('Error: STRIPE_SECRET_KEY not set');
11}
12
13echo 'Stripe configured with API version: ' . \Stripe\Stripe::$apiVersion;
14?>

Expected result: The Stripe client is configured with your test secret key from environment variables.

3

Create a customer and PaymentIntent

Build API endpoints for customer creation and payment processing.

typescript
1<?php
2require_once 'vendor/autoload.php';
3\Stripe\Stripe::setApiKey(getenv('STRIPE_SECRET_KEY'));
4
5header('Content-Type: application/json');
6$input = json_decode(file_get_contents('php://input'), true);
7
8try {
9 // Create a customer
10 $customer = \Stripe\Customer::create([
11 'email' => $input['email'],
12 'name' => $input['name'] ?? null,
13 'metadata' => [
14 'app_user_id' => $input['userId'] ?? '',
15 ],
16 ]);
17
18 // Create a PaymentIntent
19 $paymentIntent = \Stripe\PaymentIntent::create([
20 'amount' => $input['amount'], // Amount in cents
21 'currency' => 'usd',
22 'customer' => $customer->id,
23 'automatic_payment_methods' => ['enabled' => true],
24 'metadata' => [
25 'order_id' => $input['orderId'] ?? '',
26 ],
27 ]);
28
29 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.

4

Handle webhooks with signature verification

Verify the webhook signature using the raw POST body. This is the most critical security step.

typescript
1<?php
2require_once 'vendor/autoload.php';
3
4\Stripe\Stripe::setApiKey(getenv('STRIPE_SECRET_KEY'));
5$webhookSecret = getenv('STRIPE_WEBHOOK_SECRET');
6
7// Get the raw POST body (do NOT use json_decode first)
8$payload = file_get_contents('php://input');
9$sigHeader = $_SERVER['HTTP_STRIPE_SIGNATURE'] ?? '';
10
11try {
12 $event = \Stripe\Webhook::constructEvent(
13 $payload,
14 $sigHeader,
15 $webhookSecret
16 );
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}
26
27// Handle the event
28switch ($event->type) {
29 case 'payment_intent.succeeded':
30 $intent = $event->data->object;
31 error_log("Payment succeeded: {$intent->id}");
32 // Fulfill the order
33 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}
41
42http_response_code(200);
43echo json_encode(['received' => true]);
44?>

Expected result: Webhook signatures are verified and events are routed to appropriate handlers.

5

Handle Stripe errors by type

Stripe throws specific exception types for different errors. Handle each for appropriate user feedback.

typescript
1<?php
2try {
3 $paymentIntent = \Stripe\PaymentIntent::create([
4 'amount' => 5000,
5 'currency' => 'usd',
6 ]);
7} catch (\Stripe\Exception\CardException $e) {
8 // Card was declined
9 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.

6

Laravel controller integration

If you use Laravel, integrate Stripe through a controller with proper request handling.

typescript
1<?php
2// app/Http/Controllers/StripeController.php
3namespace App\Http\Controllers;
4
5use Illuminate\Http\Request;
6use Stripe\Stripe;
7use Stripe\PaymentIntent;
8use Stripe\Customer;
9use Stripe\Webhook;
10
11class StripeController extends Controller
12{
13 public function __construct()
14 {
15 Stripe::setApiKey(env('STRIPE_SECRET_KEY'));
16 }
17
18 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 }
31
32 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 }
43
44 if ($event->type === 'payment_intent.succeeded') {
45 \Log::info('Payment: ' . $event->data->object->id);
46 }
47
48 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

stripe-server.php
1<?php
2require_once 'vendor/autoload.php';
3
4\Stripe\Stripe::setApiKey(getenv('STRIPE_SECRET_KEY'));
5\Stripe\Stripe::setApiVersion('2024-12-18.acacia');
6
7header('Content-Type: application/json');
8
9$uri = $_SERVER['REQUEST_URI'];
10$method = $_SERVER['REQUEST_METHOD'];
11
12if ($uri === '/api/config' && $method === 'GET') {
13 echo json_encode(['publishableKey' => getenv('STRIPE_PUBLISHABLE_KEY')]);
14 exit;
15}
16
17if ($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}
33
34if ($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}
48
49if ($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}
72
73http_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.

ChatGPT Prompt

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.

Stripe Prompt

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.

RapidDev

Talk to an Expert

Our team has built 600+ apps. Get personalized help with your project.

Book a free consultation

Need help with your project?

Our experts have built 600+ apps and can accelerate your development. Book a free consultation — no strings attached.

Book a free consultation

We put the rapid in RapidDev

Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. We'll discuss your project and provide a custom quote at no cost.