What Is a Webhook?
If you have already read our guides to what an API is and how REST APIs work, you understand how your app asks another system a question and gets an answer back. A webhook flips that relationship around entirely, and understanding the flip is the whole idea: instead of you repeatedly asking "has anything happened yet," the other system tells you the moment something actually does.
The problem webhooks actually solve
Imagine you are waiting for a customer's bank transfer to clear before you release their order. Without webhooks, the only option is polling: your system repeatedly asks the payment gateway's API, every few seconds or minutes, "has this payment succeeded yet? has it succeeded yet? has it succeeded yet?" This works, technically, but it is wasteful, slow to react, since you only find out at the next check rather than the instant it actually happens, and it puts real, avoidable load on both systems for a question that has a genuine, specific moment it becomes true.
A webhook solves this by letting the payment gateway push the answer to you the moment it is known, rather than making you pull it repeatedly. You give the gateway a specific web address on your own server, and the instant the payment succeeds, or fails, or is refunded, their system automatically sends a message to that address, telling you exactly what happened. You find out the moment it is true, not up to several minutes later, and neither system wastes effort asking or answering a question before there is anything new to say.
What a webhook actually is, concretely
Strip away the special vocabulary and a webhook is simply this: a URL that lives on your own server, built specifically to receive a message, and a promise from another company's system that it will send a request to that URL automatically whenever a specific event happens. You are not fetching anything, you are not asking anything, you are simply sitting and waiting for your phone to ring, so to speak, and having code ready that knows what to do the moment it does.
Technically, the message that arrives is almost always a POST request, the same kind of request your own code might send to create something through a REST API, except this time you are on the receiving end instead of the sending end, and the body of that request typically contains a JSON payload describing exactly what happened: which event occurred, on which specific record, with what details.
A concrete example, from start to finish
Picture an online store using Paystack for payments. A customer completes checkout, and Paystack's own systems handle the actual charge against their card or bank. The store's website does not sit there repeatedly asking "is it done yet." Instead, when the store first connected Paystack, it registered a specific webhook address on its own server, something like https://mystore.com/webhooks/paystack, with Paystack.
The moment the payment finishes processing, successfully or otherwise, Paystack's system sends a POST request to exactly that address, carrying details: which transaction, what the outcome was, the amount, a reference number. The store's server, which has code specifically waiting to receive requests at that address, reads the message, confirms it is genuine, which matters and is covered below, and then does whatever it needs to do, mark the order as paid, trigger an email confirmation, release the item for delivery, entirely automatically, within moments of the payment actually completing, without anyone on the store's side needing to check anything manually.
Why you cannot simply trust an incoming webhook without checking it
Here is the detail that trips up almost every beginner building their first webhook handler, and it deserves to be stated plainly: a webhook address is a publicly reachable web address, which means, in principle, anyone on the internet could send a request to it, not just the genuine company you registered it with. If your code simply trusts whatever arrives at that address and says "payment successful," a bad actor could send a fake message claiming a payment succeeded when it never actually happened, and your system would happily release an order or credit an account for money it never received.
The fix is signature verification, and every reputable service that sends webhooks, Paystack, Flutterwave, WhatsApp's Cloud API, provides a way to do this. Along with the actual message, they include a signature, a piece of cryptographic proof, calculated using a secret key that only you and they know, that lets your code verify the message genuinely came from them and was not tampered with in transit. Your code recalculates what the signature should be, using your own copy of the secret, and compares it against the signature that arrived. If they match, the message is genuine. If they do not, discard it immediately and do not act on it. Skipping this check is one of the most serious, and unfortunately common, security mistakes in webhook implementations, because it is the one detail that separates "automatically trusting the internet" from "automatically trusting a specific, verified sender."
Idempotency: handling the same webhook arriving more than once
Real-world networks are unreliable, and most webhook systems compensate by retrying delivery if they do not receive a clear acknowledgement quickly enough, which means your endpoint should genuinely expect to sometimes receive the exact same event twice, or occasionally more. If your code is not written to handle this, a duplicate delivery of "payment succeeded" could result in an order being marked as paid twice, a confirmation email sent twice, or worse, something actually happening twice, like a second, duplicate item being dispatched.
The fix is checking, before acting, whether you have already processed this specific event, usually by storing the unique reference or event identifier that arrives with each webhook and checking against records you have already handled before doing anything else. This single habit, treating "have I seen this exact event before" as the very first thing your webhook code checks, prevents an entire category of subtle, embarrassing bugs that only show up occasionally, and usually at the worst possible time.
Responding quickly, and doing the real work afterward
A webhook sender expects your endpoint to respond quickly, typically within a few seconds, confirming receipt, and will generally treat a slow or missing response as a failed delivery worth retrying. This means your webhook handler should do the bare minimum work needed to acknowledge receipt and record what arrived, then hand off anything slower, sending a confirmation email, updating several related records, to a background process rather than doing all of it before responding. Trying to do everything inline, inside the same request that is supposed to respond quickly, is a common cause of webhooks that mysteriously appear to fail or arrive as duplicates, when the real cause is simply that the response took too long and the sender assumed delivery had failed.
Webhooks versus polling: when each one actually makes sense
Webhooks are not always available or appropriate. Some systems only offer polling, requiring you to ask periodically rather than being told automatically, and even where webhooks exist, it is worth keeping a periodic polling check as a safety net for anything genuinely important, payments especially, because no delivery mechanism is perfectly reliable, and a webhook that failed to arrive due to a temporary network issue on either side should not mean a payment silently goes unrecorded forever. A sensible pattern many real systems use: rely on webhooks for immediate, fast reaction, and run a much less frequent background check, once every few hours, comparing your own records against the other system's API, to catch the rare case a webhook never arrived at all.
Testing webhooks on your own computer before going live
A genuine practical problem when building this for the first time: webhook senders need a real, publicly reachable web address to send their requests to, and while you are developing on your own laptop, your code is typically only reachable from your own machine, not from the wider internet at all. Tools like ngrok solve this specific problem by creating a temporary, secure public address that forwards incoming requests straight to your local machine, letting you develop and genuinely test a webhook handler against real requests from a sandbox or test-mode sender before you have deployed anything to a real server at all. This is worth setting up early rather than trying to debug webhook logic purely by reading documentation and guessing, because seeing real, actual request payloads arrive is far more informative than imagining what they probably look like.
A worked example: diagnosing a webhook that "isn't working"
Picture a developer who has set up a webhook for order payments, and a week later a customer reports their order was never marked as paid despite the payment definitely going through. Working through this methodically rather than guessing: the first thing to check is the sending service's own dashboard, most payment gateways keep a log of every webhook delivery attempt, including whether your server responded successfully or with an error, and often the exact response your server sent back. This single log, checked first, usually tells you immediately which of a small number of possible problems actually occurred.
If the log shows the delivery was attempted and your server returned an error, the problem lives in your own code, perhaps the signature verification is failing because of a mismatched secret key between test and live mode, a genuinely common mistake. If the log shows delivery was attempted and your server never responded within the expected time, the problem is likely that your handler was doing too much slow work before responding, exactly the pattern described earlier about acknowledging quickly and deferring slower work. If the log shows no delivery was even attempted, the webhook address itself may be misconfigured in the sending service's dashboard, pointing at the wrong URL entirely, or perhaps never actually registered for this specific kind of event. Each of these three has a different fix, and none of them requires guesswork once you have actually looked at the delivery log rather than only looking at your own server's side of the story.
Webhooks for messaging platforms: a related but distinct use
Payment webhooks tell you about a single event that happened once. Messaging platform webhooks, such as WhatsApp's Cloud API, work on the same underlying mechanism, a URL on your server, a signed POST request, but tend to arrive far more frequently, potentially every time a customer sends your business a message, and your handler needs to be built with that higher, more continuous volume in mind rather than treating each arrival as a rare, special event. Our dedicated guide to the WhatsApp Cloud API covers the specific shape of these messages and how to build a handler that stays responsive under a genuinely busy conversation load, which is a meaningfully different engineering concern from handling an occasional payment confirmation.
Security beyond signature verification
Signature verification is the essential, non-negotiable check, but a few further habits are worth building in alongside it. Always use an address that starts with https, never plain http, since an unencrypted address could let the message, and any sensitive details inside it, be intercepted or altered in transit before it ever reaches your signature check. Keep your webhook secret exactly as sensitive as an API key, never in code that gets shared or published, since anyone with the secret could construct their own convincing fake signature. And log every incoming webhook, including ones that fail verification, since a sudden spike in failed verification attempts is often the first visible sign that someone is actively probing your endpoint, information worth having even if the individual attempts are all correctly rejected.
Common mistakes specific to webhooks
Skipping signature verification "for now" during testing, and forgetting to add it before going live. This is a genuinely common and genuinely serious oversight. Build verification in from the very first version of your handler, even during testing, so it is never a step you might forget to add later.
Doing slow, synchronous work inside the webhook handler itself. Sending an email, updating several related database records, calling out to yet another external API, all inside the same request that is supposed to respond within a few seconds, is a reliable way to produce mysterious, intermittent failures that only appear under real load, not in your quick manual tests.
Assuming events arrive in order. Network delivery does not guarantee that a "payment created" event arrives before a "payment succeeded" event for the same transaction, particularly under retry conditions. Design your handling to check the current actual state of a record rather than assuming events will always be processed in the sequence they logically happened.
Forgetting to handle the "event I don't recognise" case gracefully. Services occasionally add new event types over time, and a handler that crashes or errors loudly on an unfamiliar event type, rather than simply logging it and moving on, can cause unrelated failures the day a sender adds something new you were not specifically expecting.
Setting one up: the practical steps
Build a specific web address on your own server dedicated to receiving this particular kind of webhook, distinct from your regular pages. Register that address with the sending service, usually through their dashboard or during API setup, along with obtaining the secret key you will use to verify signatures. Write your handler to first verify the signature, reject anything that fails, then check whether you have already processed this specific event, then respond quickly with a simple acknowledgement, then hand off any slower, further work to run separately. Test it thoroughly using the sending service's test or sandbox mode before ever connecting it to anything real, since a webhook handler with a subtle bug is far cheaper to discover in testing than after real customer payments start flowing through it.
A quick reference for webhook essentials
Verify the signature on every incoming request before trusting anything in it. Check whether you have already processed this exact event before acting, since delivery can and will repeat. Respond quickly, within a few seconds, and defer slower work to run afterward. Always use https, never plain http. Keep a periodic polling check as a safety net for anything genuinely important, since no delivery mechanism is perfectly reliable. These five habits, applied consistently, prevent the overwhelming majority of real problems in a webhook implementation, and each one individually is simple; the discipline is remembering to apply all five together, every time, rather than skipping one under time pressure.
None of these five habits require advanced technical skill to implement correctly. They require discipline to apply every single time, including the tenth webhook handler you build once the novelty has worn off and it is tempting to skip the verification step "just this once" for something that feels low-stakes. It rarely stays low-stakes for long once real customers and real money are involved.
A closing thought on the asymmetry involved
A webhook handler is one of the few pieces of a website that runs entirely without a human watching it in the moment, triggered by an external system at a time nobody chose, and expected to make a correct decision, usually involving money or customer records, without any opportunity to double-check with a person first. That combination, automatic, consequential, and unwatched, is exactly why the discipline covered in this guide matters more here than in almost any other part of a typical integration, and it is worth treating with a level of care proportional to that reality rather than to how routine the code itself might look.
Where this shows up on our own platform
If you run a project on our platform, this exact pattern is already built and working for you: when a customer completes a payment through a connected gateway, our systems receive and verify that gateway's webhook, confirm the payment genuinely succeeded, and update your project's records automatically, crediting a wallet, marking an order paid, without you needing to build any of the verification or duplicate-handling logic described above yourself. If you are building something that needs to receive its own webhooks, from a payment gateway you have connected directly, or from WhatsApp's Cloud API, our developer documentation covers the specifics for your own project, and our guide to connecting an API to a website covers the practical side of wiring this into a real, live project from scratch.




Comments
No comments yet. Be the first to share your thoughts.