The weekend Razorpay started sending me error codes I'd never seen
I build and maintain payment flows for three Shopify stores and two WooCommerce sites. Between them, they process maybe 800 UPI transactions a day—mostly ₹500 to ₹3,000 orders for skincare, phone cases and home décor. Normal weeks, I get two or three webhook errors. I log them, check the signature, retry, move on.
Then Diwali week arrived. Traffic doubled. Razorpay started timing out on every fifth webhook. Cashfree threw signature mismatches I couldn't reproduce locally. One store recorded the same transaction three times and nearly shipped three orders to the same customer in Jaipur. I had twenty-five distinct errors by Sunday night, and my usual debugging process—read the docs, add a console.log, Google the error code—wasn't fast enough.
I'd been using Claude Sonnet 4.5 for code reviews, so I pasted the first timeout error and the webhook handler into a chat. It gave me a decent answer. Then I wondered: would Gemini or DeepSeek find the root cause faster? I opened Kryotta, copied the same error logs and handler code into three separate chats—Claude Sonnet 4.5, Gemini Pro and DeepSeek R1—and asked each one to debug. I did that for all twenty-five errors over three days, keeping notes on which model actually spotted the problem and which ones gave me generic advice I'd already tried.
This isn't a benchmark. It's a log of what worked when I needed to ship fixes during the busiest week of the year. If you're debugging UPI webhook failures right now, here's what I learned.
Error type one: webhook timeouts (Razorpay, 504 Gateway Timeout)
The problem: Razorpay sends the webhook. My server takes eleven seconds to respond because it's calling a third-party SMS API inside the handler. Razorpay times out at ten seconds, retries, I get duplicate notifications.
Which model found it: Claude Sonnet 4.5 and DeepSeek R1 both spotted the blocking call immediately. Gemini Pro mentioned it in the third paragraph, after suggesting I check my server's memory and Nginx config.
What Claude said: "Your handler is waiting for the SMS API to respond before returning a 200 to Razorpay. Move that call into a background job. Return 200 in under two seconds, then send the SMS." It gave me a Node.js snippet using setImmediate and a Redis queue.
What DeepSeek said: Basically the same diagnosis, but it showed me the exact line in my code where the await fetch(smsApiUrl) was blocking, then suggested Bull or Agenda for the job queue. Slightly more detail, same fix.
What Gemini said: It asked if my server was under load, suggested I increase the timeout in Razorpay's dashboard (you can't), then mentioned the blocking call as a "possible contributor."
The fix: I moved the SMS call into a Bull queue. Webhook handler now responds in 300 milliseconds. No more timeouts. Claude and DeepSeek tied here—both nailed it in the first response. Gemini took longer to get to the real issue.
Error type two: signature mismatch (Cashfree, "Invalid signature")
The problem: Cashfree webhook signature verification failing intermittently. I'm using the code straight from their docs. It works locally, fails in production maybe one in twenty times.
Which model found it: Gemini Pro. Claude and DeepSeek both gave me the standard checklist (check the secret, log the raw body, verify the header name). Gemini asked a better question.
What Gemini said: "Are you parsing the body as JSON before verifying the signature? If your framework is reading req.body as a parsed object, the signature will fail because Cashfree signs the raw string. Use express.raw() or body-parser.raw() for that route."
I checked. My Express app had express.json() as global middleware. The webhook route was getting a parsed body. The signature check was running against JSON.stringify(req.body), which doesn't produce the exact same string Cashfree signed (key order changes). I added express.raw({ type: 'application/json' }) to the webhook route. Mismatches stopped.
What Claude said: It gave me a checklist: verify the secret matches, log the raw payload, check the timestamp. All good advice, nothing wrong. But it didn't ask the middleware question.
What DeepSeek said: Similar checklist, plus a note that some hosting providers (Vercel, Netlify) parse the body before your code runs. Useful if I'd been on those platforms, but I wasn't.
The fix: Middleware config. Gemini won this one by asking the right question first.
Error type three: duplicate transaction IDs (WooCommerce, same order_id recorded twice)
The problem: High traffic. Razorpay sends the webhook. My handler checks if transaction_id exists in the database, doesn't find it, inserts a new row. But another webhook for the same transaction hits half a second later, also doesn't find it (because the first insert hasn't committed yet), inserts again. Customer gets charged once, I record it twice, nearly ship two orders.
Which model found it: DeepSeek R1. Claude mentioned race conditions in general terms. Gemini suggested I add a unique constraint (I already had one, it was throwing errors but not stopping the duplicate).
What DeepSeek said: "This is a race condition. Your SELECT-then-INSERT pattern isn't atomic. Use an upsert or a distributed lock. If you're on PostgreSQL, INSERT ... ON CONFLICT DO NOTHING. If MySQL, INSERT IGNORE. Or use Redis to lock on the transaction ID before inserting."
It showed me the exact PostgreSQL query:
INSERT INTO orders (transaction_id, amount, status)
VALUES ($1, $2, 'paid')
ON CONFLICT (transaction_id) DO NOTHING
RETURNING id;
What Claude said: "You might have a race condition. Check if your database query is atomic. Consider using a transaction or a lock." True, but less specific. I had to ask a follow-up to get the ON CONFLICT syntax.
What Gemini said: "Add a unique index on transaction_id." I had one. It was preventing the duplicate insert, but my code was catching the error and logging it without telling me. Gemini didn't push me toward the upsert pattern.
The fix: Upsert. DeepSeek gave me the exact query in the first answer. No follow-ups needed.
Error type four: "Payment captured" webhook arrives before "Payment authorized"
The problem: Razorpay sometimes sends events out of order. My handler expects payment.authorized first, then payment.captured. When captured arrives first, my code can't find the pending order, throws an error, Razorpay retries, I get five emails.
Which model found it: Claude Sonnet 4.5. DeepSeek suggested I check the event timestamp and sort them. Gemini said I should handle both events idempotently (correct, but not the root issue).
What Claude said: "Webhooks aren't guaranteed to arrive in order. Your handler should check the payment status from Razorpay's API if it can't find a pending order, rather than assuming the order doesn't exist. Or design your flow so both authorized and captured can create or update the order record safely."
It walked me through a state machine: order starts as pending, authorized moves it to authorized, captured moves it to paid. If captured arrives first, fetch the payment details from Razorpay, see that it's already authorized, create the order as paid directly.
What DeepSeek said: "Log the created_at timestamp from each event. Process them in order." Reasonable, but that means holding events in a queue and sorting them, which is more work than handling them idempotently.
What Gemini said: "Make sure each event handler can run multiple times without breaking things." True, and I should do that anyway. But it didn't address the out-of-order issue.
The fix: I rewrote the handler to fetch payment status from Razorpay if the order isn't in the expected state. Claude's state-machine explanation was the clearest.
Error type five: webhook payload missing expected fields (Cashfree, order_amount is null)
The problem: Cashfree sends a webhook. My code reads data.order.order_amount, but that field is null for one specific event type (PAYMENT_FAILED). My handler crashes.
Which model found it: Gemini Pro. Claude and DeepSeek both told me to add null checks (which I should), but Gemini actually explained why the field was null.
What Gemini said: "Cashfree doesn't populate order_amount in PAYMENT_FAILED events if the payment failed before the order was fully created—usually a VPA validation error or a user cancellation in the first few seconds. Check the event type first. If it's PAYMENT_FAILED, either fetch the order details from Cashfree's API or handle it as a failed attempt without recording the amount."
I checked the Cashfree docs. Buried in the event reference: "Some fields may be null depending on the event type." I added a type check and a fallback to fetch the order via API if I need the amount.
What Claude said: "Add a null check: const amount = data.order.order_amount ?? 0;" Fine, but doesn't explain why it's null.
What DeepSeek said: Similar. "Validate the payload before using it." Correct, not enlightening.
The fix: Event-type check plus API fallback. Gemini explained the why, which helped me decide how to handle it.
Patterns I noticed across twenty-five errors
I'm not going to walk through all twenty-five. You get the idea. But here's what happened across the full set:
Claude Sonnet 4.5 was best at architectural issues—timeouts, blocking calls, state machines, async patterns. When the problem was "your code is doing the right thing in the wrong order," Claude spotted it. It's also good at explaining why a fix works, which helps when you're maintaining this code in six months.
Gemini Pro asked better questions about the environment—middleware, framework quirks, payload structure. When the bug was "your setup is silently changing the data before your code sees it," Gemini found it. It's also faster and cheaper, so I used it for the simpler errors (logging issues, missing fields).
DeepSeek R1 gave the most specific code. When I needed the exact SQL query, the exact Redis command, the exact line number where the bug was, DeepSeek delivered. It's slower—some answers took twenty seconds—but the answers were detailed enough that I could copy-paste and move on.
I didn't use one model for everything. I'd paste the error and handler code into all three, skim the answers, then follow up with whichever one gave me the best starting point. That sounds inefficient, but it saved time. One model might say "check your database query," another says "check your middleware," the third says "here's the query you need." You see the full picture in two minutes instead of spending twenty minutes going down the wrong path.
The debugging flow that actually worked during Diwali week
Here's what I did for each new error:
-
Grab the logs. Razorpay and Cashfree both give you a webhook log in the dashboard. I'd copy the payload, the headers and the error message my server returned.
-
Paste into Kryotta. I'd open three chats—Claude Sonnet 4.5, Gemini Pro, DeepSeek R1—and paste the same context into all three: "Here's the webhook payload, here's my handler code, here's the error. What's wrong?"
-
Read all three answers in parallel. Claude might say "blocking call," Gemini might say "middleware issue," DeepSeek might say "race condition." I'd pick the one that matched the symptom (timeouts → blocking call, signature failures → middleware, duplicates → race condition).
-
Follow up with the winning model. Whichever model gave the best first answer, I'd ask it for the exact fix. "Show me the code." "Which Redis command?" "What's the PostgreSQL syntax?"
-
Test locally, then deploy. I'd reproduce the error with a test webhook from Razorpay's dashboard, apply the fix, verify it worked, push to production.
This isn't a rigorous process. It's what kept me shipping fixes at 11 p.m. when I had three stores processing orders and webhooks failing every few minutes. The multi-model approach worked because no single model is best at every type of bug. Claude's good at architecture, Gemini's good at environment issues, DeepSeek's good at "just give me the code."
If you're debugging UPI webhook errors right now, don't assume one model will handle everything. Use the fast, cheap one (Gemini Flash or Flash Lite) for the obvious stuff—missing fields, typos, logging issues. Use the reasoning models (Claude Sonnet, DeepSeek R1) for the hard stuff—race conditions, async bugs, state machines. You'll close tickets faster and spend less time reading generic advice.
Questions people ask
Can I use the free tier of these models, or do I need a paid plan?
Kryotta gives you access to all three—Claude Sonnet 4.5, Gemini Pro, DeepSeek R1—on the free tier with rate limits. I hit the limit once during Diwali week and upgraded to the ₹799/month plan. If you're debugging occasionally, free is fine. If you're pasting twenty-five errors in three days, you'll want paid.
Which model should I start with if I only have time to try one?
Gemini Pro. It's fast, cheap and asks good questions about your setup. If it doesn't find the issue in two answers, escalate to Claude or DeepSeek.
Do these models work with Paytm or PhonePe webhooks, or just Razorpay and Cashfree?
They work with any webhook. The debugging process is the same: paste the payload and your handler code, ask what's wrong. I've used them for Instamojo and Paytm as well. The models don't have Razorpay-specific knowledge, they're just reading your code and the error message.
How do I know if the model's answer is correct, or if it's making something up?
Test it. If the model says "use ON CONFLICT DO NOTHING," run that query locally. If it says "move the SMS call into a queue," deploy the change and check the logs. I caught two wrong answers during Diwali week—one model told me to increase a timeout that doesn't exist, another suggested a Redis command with the wrong syntax. Both were obvious when I tried them. Don't trust, verify.
If you're building or maintaining payment flows in India and you're tired of debugging webhook errors at midnight, try running the same error past two or three reasoning models before you start rewriting code. You'll find the root cause faster, and you'll learn which model is best at which type of bug. I keep Claude, Gemini and DeepSeek open in separate tabs now. It's faster than Stack Overflow and more specific than the docs.
Start debugging smarter at Kryotta—Claude, Gemini and DeepSeek in one workspace, no API juggling required.



