Webhooks
The bundle exposes POST /cashier/webhook to receive Stripe events.
Configuration
cashier:
webhook:
secret: '%env(STRIPE_WEBHOOK_SECRET)%'
tolerance: 300
events:
- customer.subscription.created
- customer.subscription.updated
- customer.subscription.deleted
- customer.deleted
- invoice.payment_succeeded
- invoice.payment_failed
- checkout.session.completed
- payment_intent.succeeded
- payment_intent.payment_failedRole of webhooks
Do not limit your integration to the browser return after payment.
Webhooks are used to:
- confirm payment server-side
- synchronize subscriptions
- archive invoices
- dispatch the bundle’s Symfony events
Built-in handlers
The bundle provides handlers for the main billing events, notably:
customer.subscription.createdcustomer.subscription.updatedcustomer.subscription.deletedcustomer.deletedinvoice.payment_succeededinvoice.payment_failedinvoice.payment_action_requiredcheckout.session.completedcheckout.session.expiredpayment_method.updatedpayment_intent.succeededpayment_intent.payment_failed
Placeholder handlers
Some handlers are empty extension points — implement via a custom handler:
InvoicePaymentActionRequiredHandler— handlesinvoice.payment_action_requiredCheckoutSessionCompletedHandler— handlescheckout.session.completed
Production configuration
Create the webhook via CLI:
php bin/console cashier:webhook --url=https://mysite.com/cashier/webhook --show-secretCopy the secret into STRIPE_WEBHOOK_SECRET.
Verify that the path matches cashier.path in cashier.yaml.
Idempotency
Stripe handlers may be called multiple times with the same event ID.
Use $event->id as a guard: store processed IDs, reject duplicates.
Exception handling
If a handler throws an exception, Stripe will retry the webhook (3 days, increasing intervals).
Catch business exceptions and return 200 if the event can be ignored.
Custom handler
use CashierBundle\Contract\WebhookHandlerInterface;
use Stripe\Event;
use Symfony\Component\DependencyInjection\Attribute\AutoconfigureTag;
#[AutoconfigureTag('cashier.webhook_handler')]
final readonly class SyncOrderHandler implements WebhookHandlerInterface
{
public function handles(): array
{
return ['payment_intent.succeeded'];
}
public function handle(Event $event): void
{
$paymentIntent = $event->data->object;
$orderId = $paymentIntent->metadata->app_order_id ?? null;
// your business logic
}
}Dispatched Symfony events
WebhookReceivedEventWebhookHandledEventSubscriptionCreatedEventSubscriptionUpdatedEventSubscriptionDeletedEventPaymentSucceededEventPaymentFailedEvent
Local Stripe CLI
php bin/console cashier:webhook:listen --forward-to=http://127.0.0.1:8000/cashier/webhookOr with --base-url to automatically build the URL:
php bin/console cashier:webhook:listen --forward-to --base-url http://localhost:8000The command:
- launches
stripe listen - retrieves the
whsec_... - can update your
.env*files - colorizes webhook entries and HTTP statuses
Reading console output
- incoming Stripe event: cyan
2xxresponse: green4xxresponse: yellow5xxresponse and CLI errors: red