Reliable integration
How to process webhooks correctly
A webhook can arrive late, twice, or at the worst moment. Build an endpoint that can handle it.
First, the short version
What is a webhook?
Get ready
What you need
Start with a small, safe foundation.
- A public HTTPS URL for the endpoint. A provider usually cannot reach your local computer directly.
- The provider documentation: the event format, signature header, and test mode. Do not skip the HTTP header.
- A signing secret in an environment variable, not in source code.
- A database or message queue where you can quickly hand off further work.
Step 1
Build an endpoint that handles repeats
The key rule is: verify, save, enqueue the work, respond. Do not do all the work inside the HTTP request.
1. Accept only the intended request
- Create a separate POST route. Do not put it behind a regular form or user login.
- Read the raw request body. The signature is often calculated from it, not from a PHP field you already changed.
- Check the event type and identifier. Log unknown types and ignore them safely.
#[Route('/webhooks/payment', methods: ['POST'])] Symfony routing 2. Verify the signature before using data
- Follow the provider documentation exactly. It often uses an HMAC signature and timestamp.
- Compare signatures safely. On failure, return 400 or 401 and do not process the event.
- Never send the signing secret to the browser, logs, or an error response.
hash_equals($expectedSignature, $receivedSignature) PHP: hash_equals 3. Process the event outside the response
- Store the event ID with a unique constraint. This gives you idempotence: the same event changes nothing a second time.
- Put only the data needed for the next step on the queue. Store the large original payload separately if you need it for investigation.
- After saving successfully, quickly return status 200 or 204. Leave long work to a consumer.
php bin/console messenger:consume async -vv Symfony Messenger Step 2
Check the whole flow
Do not wait for the first real payment. Use the provider test event.
-
Send a test event
Find the webhook test in the service administration. You expect a quick 2xx response and a record that it was received.
-
Send it one more time
The log should show the second attempt, but the order or payment must not change a second time. This checks idempotence.
-
Check the queue and result
The worker must finish the task. On failure, you should see the reason and a safe plan for another attempt.
php bin/console messenger:failed:show
If something goes wrong
Common problems
The provider repeats the same event
This is normal after an outage or slow response. Store a unique event ID and process it only once. Idempotence helps here.
The signature does not match
Check the signing secret, raw request body, and header name. Do not bypass verification just to make a test pass.
The endpoint is slow or returns 500
Return success only after the event is safely saved. Move emails, API calls, and other slow work to a message queue.
A failed task disappears
Set limited retries, logging, and a failed transport. Endless retries can make an error and its cost worse.
Done
Your webhook has a solid foundation.
Your webhook is now verified, processed once, and leaves long work to the queue.