Handling out-of-order webhook delivery with monotonic timestamps

Webhook delivery networks offer no ordering guarantees. A payment.succeeded event can easily arrive at your server before a delayed payment.created event, overwriting final state with initial state.

final class PaymentWebhookHandler
{
    public function handle(array $payload): void
    {
        $order = $this->repository->find($payload['order_id']);
        $eventTimestamp = (int) $payload['timestamp'];

        // Reject stale out-of-order transitions:
        if ($order->lastEventTimestamp >= $eventTimestamp) {
            return; // Idempotently acknowledge and drop stale event
        }

        $order->applyStateChange($payload['status'], $eventTimestamp);
        $this->repository->save($order);
    }
}

Persisting the provider’s event timestamp or monotonic sequence number on the entity turns out-of-order processing into a benign no-op without requiring distributed locks.