Reliability
- Retries and backoff — exponential with full jitter, capped, Retry-After-aware
(
server.tries,server.backoff).no_retry_on_4xxkeeps a permanent400/410from being retried for hours while408/425/429still 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) — afterplatform.circuit_breaker.thresholdconsecutive final failures an endpoint auto-disables and aPushery\Webhooks\Events\WebhookEndpointAutoDisabledevent 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 withWebhooks::enable($subscription)(which also clears the streak), typically wired to theWebhookEndpointAutoDisabledevent or an operator action. - Rate limiting (
platform.rate_limit.enabled, on by default) — a per-subscription outbound cap (platform.rate_limit.max_per_minute, default60) 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.