Stripe Experts
Shopify IntegrationPlatformsIndustriesHire UsBlog
Book a Free Consultation
Stripe Experts

Trusted Stripe integration services for SaaS, marketplaces, and e-commerce.

Company

  • About
  • Blog
  • Contact

Services

  • All Services
  • Stripe Checkout
  • Stripe Connect
  • Stripe Billing

Hire Us

  • All Hiring Options
  • Hire a Stripe Expert
  • Hire a Developer
  • Hire a Stripe Consultant
  • Migrate to Stripe

Industries

  • SaaS
  • Marketplace
  • Healthcare
  • Fintech

Legal

  • Privacy Policy
  • Terms of Service

© 2026 Stripe Experts. All rights reserved.

Not affiliated with Stripe, Inc. Independent Stripe integration partner.

Powered by mzkzeeshan

bussiness@mzkzeeshan.com

  1. Home
  2. Blog
  3. Stripe Tutorials
  4. Stripe in PHP: Checkout Sessions and Webhook Verification Done Right
Stripe Tutorials

Stripe in PHP: Checkout Sessions and Webhook Verification Done Right

The raw-body problem bites PHP integrations harder than most. Here's the correct shape.

Z

Zeeshan

Founder · Published Jul 6, 2026

PHP has a first-class Stripe SDK and the happy path is short. The part that reliably goes wrong is webhook signature verification, because several PHP frameworks parse and re-serialize the request body before your handler ever sees it, and a re-serialized body will not match the signature.

Creating a Checkout Session

<?php
require 'vendor/autoload.php';

$stripe = new \Stripe\StripeClient(getenv('STRIPE_SECRET_KEY'));

$session = $stripe->checkout->sessions->create([
    'mode' => 'payment',
    'line_items' => [[
        'price' => 'price_123',
        'quantity' => 1,
    ]],
    'success_url' => getenv('SITE_URL') . '/success?session_id={CHECKOUT_SESSION_ID}',
    'cancel_url'  => getenv('SITE_URL') . '/cancel',
]);

header('Location: ' . $session->url, true, 303);
exit;

The 303 status is deliberate: it forces the browser to follow with GET, which avoids re-submitting the form if the customer navigates back.

Verifying the Webhook Signature

Stripe signs the exact bytes it sent. Read the body with file_get_contents('php://input') and hand those bytes to the SDK untouched: do not json_decode first and re-encode.

<?php
$payload   = file_get_contents('php://input');
$signature = $_SERVER['HTTP_STRIPE_SIGNATURE'] ?? '';

try {
    $event = \Stripe\Webhook::constructEvent(
        $payload,
        $signature,
        getenv('STRIPE_WEBHOOK_SECRET')
    );
} catch (\Stripe\Exception\SignatureVerificationException $e) {
    http_response_code(400);
    exit;
}

// Acknowledge fast, then do the slow work out of band.
http_response_code(200);

if ($event->type === 'checkout.session.completed') {
    enqueue_fulfillment($event->data->object->id);
}

Framework-Specific Gotchas

  • Laravel: exclude the webhook route from CSRF verification, and read the raw body via $request->getContent()
  • Symfony: $request->getContent() gives you the raw string; avoid any listener that normalizes the payload first
  • Any framework behind a proxy: confirm the proxy is not re-encoding or stripping the body, which produces the same symptom

The signature failure has a distinctive smell: it works perfectly with a locally replayed payload and fails intermittently in production. That is almost always a middleware or proxy touching the bytes, not a wrong secret.

Acknowledge First, Work Later

Stripe retries a webhook if it does not receive a 2xx quickly. If fulfillment involves sending mail, generating a PDF, or calling a slow third party, push that onto a queue and return 200 immediately. Otherwise a slow job produces retries, and retries produce duplicate fulfillment unless your handler is idempotent.

#php#checkout#webhooks

Stripe insights, monthly

One email a month, no spam, unsubscribe anytime.

No spam, unsubscribe anytime.

On This Page

  • Creating a Checkout Session
  • Verifying the Webhook Signature
  • Framework-Specific Gotchas
  • Acknowledge First, Work Later

Related Services

Stripe Checkout

Fully hosted, brand-matched Stripe Checkout, live in days.

Learn more

Webhooks

Production-grade Stripe webhook handling, verified and monitored.

Learn more

API Integration

Custom Stripe API integration for your exact business logic.

Learn more

Related Reading

Stripe Tutorials

Subscriptions in Django with Stripe Billing

Let Stripe own the billing state and Django own the entitlement. Mixing the two is where it goes wrong.

Aug 10, 2026Read
Stripe Tutorials

Integrating Stripe Payment Element in React Without the Common Pitfalls

The Elements provider, the client secret lifecycle, and the re-render bug that breaks most first attempts.

Jun 22, 2026Read
Stripe Tutorials

Adding Stripe Payments to a Shopify Store Beyond the Default Plugin

Shopify Payments covers standard storefronts well: here's where it stops being enough.

Apr 13, 2026Read