A public question asks how to stop a Laravel app from processing the same webhook event twice. We cannot see that application, do not represent its author, and do not claim to fix any platform. The adjacent problem in identity onboarding is concrete, though. A provider retries a callback, your handler runs twice, and a permission is granted twice or a later correction is overwritten. Your decision is where the deduplication guarantee lives: in a queue, in a cache, or in the same database transaction that changes verification state. This article uses the checked local Python fixture to show one answer and its limits. The fixture is not Laravel code, and its permission flag is not a real business permission.
The problem and the decision
Retries are normal webhook behavior. A sender that times out, or receives an unexpected response, may deliver the same event again. If your handler records the event in one step and changes state in another, two concurrent deliveries can both pass the check. The decision for your application is whether duplicate protection is enforced atomically, together with the state change, or only as best effort. The rest of this article argues for the atomic version and tests it locally.
What the fixture actually implements
The downloadable verification_delivery example stores sessions and events in SQLite. The events table has a primary key on (session, event), so the same event ID can be recorded only once per session. The event handler opens a BEGIN IMMEDIATE transaction. It checks that the tenant and subject match the session, returning BINDING_MISMATCH if they do not. If the event ID already exists, it returns the current state unchanged. Otherwise it records the event and updates the status in the same transaction.
The handler accepts only synthetic input. Event IDs must start with fixture-, mode must be synthetic_fixture, and source must be fixture_callback. Any other input raises PRODUCTION_SIGNATURE_VERIFIER_NOT_IMPLEMENTED. No real signature is verified.
Worked hypothetical scenario and expected outputs
Hypothetically, a seller onboarding flow creates fixture-session with a deadline of 60. The event fixture-approved then arrives with status Approved and explicit policy=True. The expected approval output is: status approved, deadline 60, permission_granted true, grant_count 1, live_permission_changed false, policy_version 1, allow_action true.
The same event is then delivered again. The duplicate output is identical, and grant_count stays at 1.
Next, a distinct Declined event arrives. The correction output is: status declined, permission_granted false, grant_count 1. The local flag is revoked, and the counter keeps its history.
In a separate run with no callback, recover at time 61 returns: status expired, permission_granted false, grant_count 0, next review_or_authorized_provider_refresh, provider_polled false.
Failure handling you still own
Unknown statuses become unknown. They are never treated as approved. Once a session is approved, declined or expired, the only change it accepts is Approved followed by Declined; any other event leaves it unchanged.
An event for a missing session returns BINDING_MISMATCH. A read of a missing session returns SESSION_NOT_FOUND. Recover expires the session locally and does not poll a provider.
In a proposed Laravel design, you would choose your own equivalents. One option is a unique constraint on provider event ID plus session, written in the same database transaction as the status update. You would also need provider-specific signature validation before that write, a fast acknowledgement, and bounded status reconciliation through an authorized route. The fixture does not demonstrate any of these, and none of this is tested Laravel behavior.
Proposed design versus implemented behavior
What the fixture implements: idempotent synthetic events, binding checks, a single local grant, revocation on a later Declined event, and local expiry.
What remains proposal: real signature checks, queue semantics, retries against a provider, and mapping a local grant to real seller permissions. Approved is not unconditional permission. A synthetic permission_granted value is a fixture record, not live business permission.
Acceptance checklist
Before adapting this pattern, confirm each item below.
- A repeated event returns unchanged state with grant_count 1.
- Dedup and the state change commit in one transaction.
- A tenant or subject mismatch is rejected.
- A later Declined event clears the grant.
- Unknown statuses never grant anything.
- A missed deadline yields expired with provider_polled false.
- Restarting with the same SQLite database preserves state.
- Real signature verification is listed as an open requirement.
Run the guide's unittest commands first. Then decide whether a sandbox request fits your task.
Run the checked synthetic example
Download and extract https://gridzen.ai/downloads/verification-delivery.zip, enter verification_delivery, then run with Python 3.10+. This local example makes no network calls. Use a fresh database for an independent identity run.
python3 delivery.py identity --db /tmp/gridzen-article-fixture.sqlite
Sources and scope
https://gridzen.ai/guides/missing-verification-webhook/
https://gridzen.ai/guides/current-capabilities/
Based on the checked source snapshot and local fixture; proposed application rules are design advice, not proven production behavior. Written with AI assistance and reviewed independently.
curl https://gridzen.ai/console/api/quickstart/trial \
-H 'Content-Type: application/json' \
-d '{"task": "identity_onboarding", "scenario": "success"}'
This is synthetic integration testing, not identity verification or production authorization. Real payments are disabled.
Create a free sandbox account ยท Integration guide
Written and tested by the GridZen team with AI assistance.
Top comments (0)