> ## Documentation Index
> Fetch the complete documentation index at: https://docs.subscribd.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Webhooks

> Handle gateway events and verify webhook signatures

# Webhooks

Subscribd automatically registers a webhook endpoint for each configured gateway at `/subscribd/webhook/{gateway}`. Incoming payloads are verified, deduplicated, and translated into Laravel events that your application can listen to.

## Endpoint URLs

| Gateway    | Endpoint                                           |
| ---------- | -------------------------------------------------- |
| Stripe     | `https://yourapp.com/subscribd/webhook/stripe`     |
| PayPal     | `https://yourapp.com/subscribd/webhook/paypal`     |
| Braintree  | `https://yourapp.com/subscribd/webhook/braintree`  |
| Paddle     | `https://yourapp.com/subscribd/webhook/paddle`     |
| FastSpring | `https://yourapp.com/subscribd/webhook/fastspring` |
| Square     | `https://yourapp.com/subscribd/webhook/square`     |

All endpoints are automatically excluded from Laravel's CSRF protection.

## Signature verification

Subscribd verifies every incoming webhook signature before processing. Configure the appropriate secret for each gateway:

```env theme={null}
# Stripe
STRIPE_WEBHOOK_SECRET=whsec_...

# PayPal
PAYPAL_WEBHOOK_ID=...

# Braintree
BRAINTREE_WEBHOOK_SECRET=...

# Paddle
PADDLE_WEBHOOK_SECRET=...

# FastSpring
FASTSPRING_WEBHOOK_SECRET=...

# Square
SQUARE_WEBHOOK_SIGNATURE_KEY=...
```

Requests with invalid signatures receive a `403` response and are never processed. Do not disable signature verification in production.

## Idempotency

Subscribd records every processed webhook event in `subscribd_webhook_events`. If a gateway retries delivery, the duplicate payload is detected by its event ID and acknowledged without reprocessing. This table is pruned automatically according to `subscribd.webhook.retention_days` (default: `30`).

## Events dispatched by webhook handlers

When a verified webhook payload is processed, Subscribd dispatches the corresponding Laravel event. Your listeners receive a fully hydrated local model — you do not need to interact with the gateway API inside your listener.

| Gateway event                          | Subscribd event        |
| -------------------------------------- | ---------------------- |
| Subscription created / activated       | `SubscriptionCreated`  |
| Subscription updated                   | `SubscriptionUpdated`  |
| Subscription canceled                  | `SubscriptionCanceled` |
| Subscription past due                  | `SubscriptionPastDue`  |
| Trial ending soon                      | `TrialEndingSoon`      |
| Invoice paid                           | `InvoicePaid`          |
| Invoice payment failed                 | `InvoicePaymentFailed` |
| Checkout completed (Paddle/FastSpring) | `SubscriptionCreated`  |

See [Hooks and Events](/advanced/hooks-and-events) for the full event reference and listener registration.

## Configuring webhooks per gateway

### Stripe

In the [Stripe Dashboard](https://dashboard.stripe.com/webhooks), create an endpoint pointing to `https://yourapp.com/subscribd/webhook/stripe` and enable:

* `customer.subscription.created`
* `customer.subscription.updated`
* `customer.subscription.deleted`
* `invoice.paid`
* `invoice.payment_failed`
* `invoice.payment_action_required`
* `checkout.session.completed`

Copy the **Signing secret** and set it as `STRIPE_WEBHOOK_SECRET`.

### PayPal

In your [PayPal Developer App](https://developer.paypal.com/dashboard/applications), add a webhook for `https://yourapp.com/subscribd/webhook/paypal`. Enable:

* `BILLING.SUBSCRIPTION.CREATED`
* `BILLING.SUBSCRIPTION.ACTIVATED`
* `BILLING.SUBSCRIPTION.UPDATED`
* `BILLING.SUBSCRIPTION.CANCELLED`
* `PAYMENT.SALE.COMPLETED`
* `PAYMENT.SALE.DENIED`

Set the webhook ID as `PAYPAL_WEBHOOK_ID`.

### Braintree

In the Braintree Control Panel under **Settings > Webhooks**, add `https://yourapp.com/subscribd/webhook/braintree` and enable:

* `subscription_charged_successfully`
* `subscription_charged_unsuccessfully`
* `subscription_canceled`
* `subscription_expired`
* `subscription_went_past_due`
* `subscription_trial_ended`

### Paddle

In the [Paddle Dashboard](https://vendors.paddle.com/notifications), create a notification destination for `https://yourapp.com/subscribd/webhook/paddle` and enable:

* `subscription.created`
* `subscription.updated`
* `subscription.canceled`
* `subscription.paused`
* `subscription.resumed`
* `transaction.completed`
* `transaction.payment_failed`

Copy the **Secret key** and set it as `PADDLE_WEBHOOK_SECRET`.

### FastSpring

In your FastSpring Dashboard under **Developer > Webhooks**, configure `https://yourapp.com/subscribd/webhook/fastspring` and enable:

* `subscription.activated`
* `subscription.updated`
* `subscription.canceled`
* `subscription.deactivated`
* `order.completed`
* `order.failed`

Set the **HMAC Secret** as `FASTSPRING_WEBHOOK_SECRET`.

### Square

In the [Square Developer Dashboard](https://developer.squareup.com/apps), configure `https://yourapp.com/subscribd/webhook/square` and enable:

* `subscription.created`
* `subscription.updated`
* `subscription.canceled`
* `subscription.expired`
* `invoice.payment_made`
* `invoice.payment_failed`
* `payment.completed`
* `payment.failed`

Copy the **Signature Key** and set it as `SQUARE_WEBHOOK_SIGNATURE_KEY`.

## Local development

Use the [Stripe CLI](https://stripe.com/docs/stripe-cli) to forward Stripe webhooks to your local app:

```bash theme={null}
stripe listen --forward-to http://localhost:8000/subscribd/webhook/stripe
```

The CLI outputs a webhook signing secret — set it as `STRIPE_WEBHOOK_SECRET` in your `.env` while developing locally.

For other gateways, tools like [ngrok](https://ngrok.com) or [Expose](https://expose.dev) can create a public tunnel to your local server.

## Queue configuration

Webhook processing runs on the queue. Ensure your queue worker is running:

```bash theme={null}
php artisan queue:work
```

All webhook jobs use the queue connection and queue name configured in `subscribd.queue`:

```php theme={null}
// config/subscribd.php
'queue' => [
    'connection' => env('SUBSCRIBD_QUEUE_CONNECTION', 'default'),
    'queue'      => env('SUBSCRIBD_QUEUE', 'default'),
],
```

For production, a dedicated `billing` queue is recommended:

```env theme={null}
SUBSCRIBD_QUEUE=billing
```

## Replaying webhook events

Replay a previously received webhook event by its ID:

```bash theme={null}
php artisan subscribd:webhook:replay {id}
```

This re-dispatches the stored payload through the full processing pipeline, including deduplication bypass. Useful for recovering from a failed listener or missed processing window.

## Next steps

* **[Hooks and Events](/advanced/hooks-and-events)** — Full event reference and listener registration
* **[Dunning](/advanced/dunning)** — Automatic retry logic for failed payments
