REST, GraphQL, WebSockets — all three have one thing in common. The client initiates. You open the app, you trigger the request, the server responds. Even WebSockets, where the server pushes data freely, require the client to open the connection first.
Webhooks flip this completely. You give a server your URL and tell it what event to watch for. When that event happens, the server calls you. You don't ask. You don't poll. You just receive.
The Problem Webhooks Solve
You've integrated Razorpay into your app. A user pays. Your app needs to know the payment succeeded so it can unlock access, send a confirmation email, and update the database.
With REST, your only option is to keep asking Razorpay: "Did the payment go through?" over and over until you get a yes. This is polling, and you already know it's wasteful.
With WebSockets, you'd maintain a persistent open connection just to wait for one event. That's the wrong tool for something that fires once and is done.
Webhooks solve this with a simple idea: instead of you calling Razorpay, Razorpay calls you.
How Webhooks Work
The setup is three steps.
You register your URL with Razorpay and tell it which events to watch. When a payment is captured, Razorpay sends a POST request to your URL with a payload describing what happened:
{
"event": "payment.captured",
"payload": {
"payment_id": "pay_abc123",
"amount": 49900,
"currency": "INR",
"order_id": "order_xyz"
}
}
Your server receives this, processes it, and responds with a 200 OK to tell Razorpay it was received successfully. That's the entire flow.
The Security Problem (And How to Solve It)
Here's the issue. Your webhook URL is public. Anyone who finds it can send a fake POST request pretending to be Razorpay, triggering order fulfillment without a real payment.
Razorpay solves this by sending a signature alongside every webhook — an HMAC hash generated using your webhook secret and the payload content. Your server recomputes the hash using the same secret and compares. If they match, the request is genuine.
import hmac
import hashlib
def verify_webhook(payload_body, received_signature, secret):
# Recompute the expected signature using your secret
expected = hmac.new(
secret.encode(),
payload_body,
hashlib.sha256
).hexdigest()
# Compare — if they match, the request is genuinely from Razorpay
return hmac.compare_digest(expected, received_signature)
Never skip this step. Always verify the signature before processing any webhook payload.
When to Use Webhooks, When Not To
Use Webhooks when an external service needs to notify you about an event: payment confirmed, email delivered, GitHub PR merged, CI build finished. Any situation where something happens outside your system and you need to react to it.
Skip Webhooks when you need immediate confirmation within the same user interaction. Webhooks are asynchronous — the notification arrives after the fact, not inline with the user's action. For synchronous confirmation (showing the user a success screen the moment they pay), use REST to poll the payment status directly.
What You Now Understand
Every API before this one required the client to speak first. Webhooks hand that control to the server: you register a URL, an event fires, the server calls you with the details.
The pattern is everywhere once you see it. GitHub calling your CI server when code is pushed. Stripe calling your backend when a subscription renews. Twilio calling your app when an SMS is delivered. In every case, the event happens in someone else's system, and webhooks are how they tell you about it.
Your next step: go to any service you already use (GitHub, Stripe, Razorpay) and find their webhook settings. Register a free endpoint from webhook.site as your URL, trigger an event, and watch the payload arrive in real time. Seeing your first real webhook hit is the moment the model becomes completely concrete.




Top comments (0)