Skip to main content

Reliability

  • Retries and backoff — exponential with full jitter, capped, Retry-After-aware (server.tries, server.backoff). no_retry_on_4xx keeps a permanent 400/410 from being retried for hours while 408/425/429 still are. See Sending.
  • Idempotency — a stable per-event id (the Standard Webhooks webhook-id), preserved across redelivery, and two-tier inbound dedupe on receipt.
  • Circuit breaker (platform.circuit_breaker.enabled, on by default) — after platform.circuit_breaker.threshold consecutive final failures an endpoint auto-disables and a Pushery\Webhooks\Events\WebhookEndpointAutoDisabled event fires. While an endpoint is still active a single successful delivery resets the streak, but once it is disabled it receives no further traffic and so does not self-recover — bring it back with Webhooks::enable($subscription) (which also clears the streak), typically wired to the WebhookEndpointAutoDisabled event or an operator action.
  • Rate limiting (platform.rate_limit.enabled, on by default) — a per-subscription outbound cap (platform.rate_limit.max_per_minute, default 60) and an optional per-source inbound cap keep one slow endpoint from starving the queue. The outbound cap SHAPES traffic rather than dropping it: an over-limit delivery is logged, announced (Pushery\Webhooks\Events\WebhookDeliveryRateLimited) and enqueued with a delay, so a burst is spread across the following minutes instead of being lost.
  • Lifecycle events — every delivery announces its fate. Which family to listen to depends on whether you run the Platform layer; see Events.

Retries need a real queue

Every retry in this package is a release() back onto the queue the job came from. The sync connection has no worker, so a released job is never picked up again — the job runs exactly once, and a delivery that fails in a retryable way ends there instead of using its configured tries.

The package does not pretend otherwise. Such a delivery reaches a terminal state and reports Pushery\Webhooks\Server\Exceptions\QueueCannotRetry, whose message leads with the original transport failure and then names the queue — so the stored error still says "certificate has expired", and the queue is given as the reason that failure became final rather than as a replacement for it. webhooks:preflight warns when the server layer resolves to sync.

Use database, redis or sqs wherever deliveries matter.

Retention

php artisan webhooks:partition-maintenance (scheduled daily) ages the delivery log out past platform.retention_months.

On PostgreSQL the log is monthly range-partitioned, so a whole old month is dropped as a metadata operation, not a bulk DELETE, and the command also provisions upcoming partitions and drains any delivery that landed in the catch-all default partition while the schedule was behind — so a lapse in the cron cannot permanently stop either.

On MySQL the log is a flat table (kept so the ON DELETE CASCADE GDPR guarantee holds — MySQL cannot have both a foreign key and partitioning), and retention is an indexed, chunked DELETE over the same window; the same command, the same cutoff.

Below ~1M deliveries/month the difference is invisible; see Choosing your database.

Retention prunes rows. If you enabled large-payload offload, the content-addressed disk objects are reclaimed separately — by a disk lifecycle policy, or by php artisan webhooks:prune-orphaned-payloads. See the command reference.