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.