# How to use Stripe API with PHP

- Tool: Stripe
- Difficulty: Beginner
- Time required: 15 minutes
- Compatibility: Stripe PHP SDK v13+, PHP 8.1+, Laravel 10+
- Last updated: March 2026

## 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.

## Before you start

- 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.

```
# Install the Stripe PHP SDK
composer require stripe/stripe-php

# For Laravel, also install:
# composer require laravel/cashier (for subscriptions)
# 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.

```
<?php
require_once 'vendor/autoload.php';

// Load from environment variable
\Stripe\Stripe::setApiKey(getenv('STRIPE_SECRET_KEY'));
\Stripe\Stripe::setApiVersion('2024-12-18.acacia');

// Verify configuration
if (!getenv('STRIPE_SECRET_KEY')) {
    die('Error: STRIPE_SECRET_KEY not set');
}

echo 'Stripe configured with API version: ' . \Stripe\Stripe::$apiVersion;
?>
```

**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.

```
<?php
require_once 'vendor/autoload.php';
\Stripe\Stripe::setApiKey(getenv('STRIPE_SECRET_KEY'));

header('Content-Type: application/json');
$input = json_decode(file_get_contents('php://input'), true);

try {
    // Create a customer
    $customer = \Stripe\Customer::create([
        'email' => $input['email'],
        'name' => $input['name'] ?? null,
        'metadata' => [
            'app_user_id' => $input['userId'] ?? '',
        ],
    ]);

    // Create a PaymentIntent
    $paymentIntent = \Stripe\PaymentIntent::create([
        'amount' => $input['amount'], // Amount in cents
        'currency' => 'usd',
        'customer' => $customer->id,
        'automatic_payment_methods' => ['enabled' => true],
        'metadata' => [
            'order_id' => $input['orderId'] ?? '',
        ],
    ]);

    echo json_encode([
        'customerId' => $customer->id,
        'clientSecret' => $paymentIntent->client_secret,
    ]);
} catch (\Stripe\Exception\ApiErrorException $e) {
    http_response_code(400);
    echo json_encode(['error' => $e->getMessage()]);
}
?>
```

**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.

```
<?php
require_once 'vendor/autoload.php';

\Stripe\Stripe::setApiKey(getenv('STRIPE_SECRET_KEY'));
$webhookSecret = getenv('STRIPE_WEBHOOK_SECRET');

// Get the raw POST body (do NOT use json_decode first)
$payload = file_get_contents('php://input');
$sigHeader = $_SERVER['HTTP_STRIPE_SIGNATURE'] ?? '';

try {
    $event = \Stripe\Webhook::constructEvent(
        $payload,
        $sigHeader,
        $webhookSecret
    );
} catch (\UnexpectedValueException $e) {
    http_response_code(400);
    echo 'Invalid payload';
    exit;
} catch (\Stripe\Exception\SignatureVerificationException $e) {
    http_response_code(400);
    echo 'Invalid signature';
    exit;
}

// Handle the event
switch ($event->type) {
    case 'payment_intent.succeeded':
        $intent = $event->data->object;
        error_log("Payment succeeded: {$intent->id}");
        // Fulfill the order
        break;
    case 'payment_intent.payment_failed':
        $intent = $event->data->object;
        error_log("Payment failed: {$intent->id}");
        break;
    default:
        error_log("Unhandled event: {$event->type}");
}

http_response_code(200);
echo json_encode(['received' => true]);
?>
```

**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.

```
<?php
try {
    $paymentIntent = \Stripe\PaymentIntent::create([
        'amount' => 5000,
        'currency' => 'usd',
    ]);
} catch (\Stripe\Exception\CardException $e) {
    // Card was declined
    http_response_code(402);
    echo json_encode([
        'error' => $e->getMessage(),
        'code' => $e->getStripeCode(),
        'decline_code' => $e->getDeclineCode(),
    ]);
} catch (\Stripe\Exception\RateLimitException $e) {
    http_response_code(429);
    echo json_encode(['error' => 'Too many requests. Please retry.']);
} catch (\Stripe\Exception\InvalidRequestException $e) {
    http_response_code(400);
    echo json_encode(['error' => $e->getMessage()]);
} catch (\Stripe\Exception\AuthenticationException $e) {
    http_response_code(500);
    echo json_encode(['error' => 'Payment configuration error.']);
} catch (\Stripe\Exception\ApiConnectionException $e) {
    http_response_code(503);
    echo json_encode(['error' => 'Payment service unavailable.']);
} catch (\Stripe\Exception\ApiErrorException $e) {
    http_response_code(500);
    echo json_encode(['error' => 'Unexpected payment error.']);
}
?>
```

**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.

```
<?php
// app/Http/Controllers/StripeController.php
namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Stripe\Stripe;
use Stripe\PaymentIntent;
use Stripe\Customer;
use Stripe\Webhook;

class StripeController extends Controller
{
    public function __construct()
    {
        Stripe::setApiKey(env('STRIPE_SECRET_KEY'));
    }

    public function createPaymentIntent(Request $request)
    {
        try {
            $intent = PaymentIntent::create([
                'amount' => $request->input('amount'),
                'currency' => 'usd',
                'automatic_payment_methods' => ['enabled' => true],
            ]);
            return response()->json(['clientSecret' => $intent->client_secret]);
        } catch (\Stripe\Exception\ApiErrorException $e) {
            return response()->json(['error' => $e->getMessage()], 400);
        }
    }

    public function webhook(Request $request)
    {
        $payload = $request->getContent();
        $sig = $request->header('Stripe-Signature');
        try {
            $event = Webhook::constructEvent(
                $payload, $sig, env('STRIPE_WEBHOOK_SECRET')
            );
        } catch (\Exception $e) {
            return response('Invalid', 400);
        }

        if ($event->type === 'payment_intent.succeeded') {
            \Log::info('Payment: ' . $event->data->object->id);
        }

        return response()->json(['received' => true]);
    }
}
?>
```

**Expected result:** Laravel controller handles PaymentIntent creation and webhook verification using the Stripe SDK.

## Complete code example

File: `stripe-server.php`

```php
<?php
require_once 'vendor/autoload.php';

\Stripe\Stripe::setApiKey(getenv('STRIPE_SECRET_KEY'));
\Stripe\Stripe::setApiVersion('2024-12-18.acacia');

header('Content-Type: application/json');

$uri = $_SERVER['REQUEST_URI'];
$method = $_SERVER['REQUEST_METHOD'];

if ($uri === '/api/config' && $method === 'GET') {
    echo json_encode(['publishableKey' => getenv('STRIPE_PUBLISHABLE_KEY')]);
    exit;
}

if ($uri === '/api/create-payment-intent' && $method === 'POST') {
    $input = json_decode(file_get_contents('php://input'), true);
    try {
        $intent = \Stripe\PaymentIntent::create([
            'amount' => $input['amount'],
            'currency' => 'usd',
            'customer' => $input['customerId'] ?? null,
            'automatic_payment_methods' => ['enabled' => true],
        ]);
        echo json_encode(['clientSecret' => $intent->client_secret]);
    } catch (\Stripe\Exception\ApiErrorException $e) {
        http_response_code(400);
        echo json_encode(['error' => $e->getMessage()]);
    }
    exit;
}

if ($uri === '/api/customers' && $method === 'POST') {
    $input = json_decode(file_get_contents('php://input'), true);
    try {
        $customer = \Stripe\Customer::create([
            'email' => $input['email'],
            'name' => $input['name'] ?? null,
        ]);
        echo json_encode(['customerId' => $customer->id]);
    } catch (\Stripe\Exception\ApiErrorException $e) {
        http_response_code(400);
        echo json_encode(['error' => $e->getMessage()]);
    }
    exit;
}

if ($uri === '/webhook' && $method === 'POST') {
    $payload = file_get_contents('php://input');
    $sig = $_SERVER['HTTP_STRIPE_SIGNATURE'] ?? '';
    try {
        $event = \Stripe\Webhook::constructEvent(
            $payload, $sig, getenv('STRIPE_WEBHOOK_SECRET')
        );
    } catch (\Exception $e) {
        http_response_code(400);
        echo 'Webhook error: ' . $e->getMessage();
        exit;
    }
    switch ($event->type) {
        case 'payment_intent.succeeded':
            error_log('Payment: ' . $event->data->object->id);
            break;
        case 'payment_intent.payment_failed':
            error_log('Failed: ' . $event->data->object->id);
            break;
    }
    echo json_encode(['received' => true]);
    exit;
}

http_response_code(404);
echo json_encode(['error' => 'Not found']);
?>
```

## Common mistakes

- **Using json_decode on the POST body before webhook signature verification** — undefined Fix: Use file_get_contents('php://input') to get raw bytes for Webhook::constructEvent. JSON parsing alters the payload and breaks verification.
- **Hardcoding API keys in PHP files** — undefined Fix: Use getenv('STRIPE_SECRET_KEY') or Laravel's env() function. Store keys in .env files excluded from version control.
- **Not catching specific Stripe exception types** — undefined Fix: Catch CardException, RateLimitException, InvalidRequestException separately for appropriate error responses.
- **Not disabling CSRF protection for the webhook route in Laravel** — undefined Fix: 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

## 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.

---

Source: https://www.rapidevelopers.com/stripe-guide/how-to-use-stripe-api-with-php
© RapidDev — https://www.rapidevelopers.com/stripe-guide/how-to-use-stripe-api-with-php
