instant updates from route handler to frontend

So I have a webhook server that lets me know when a PayPal invoice has been paid. And I have a dynamic route (/transaction/{id}) I want to immediately change my frontend when this invoice gets paid on the correlating id. Rn I have a use state that checks for transaction status changes in mongodb. So since the webhook runs on a route handler, I’m able to update the transaction’s state in mongodb, but Im not able to let the frontend use effect know that it’s been updating. I don’t really want to poll as I want this flow to be as instant as possible, the user pays invoice in PayPal, frontend changes. How could I do this?
24 Replies
Ian
Ian7mo ago
Websockets, long polling, and server sent events would all solve this issue, and would feel nearly instant to the user. 🙂
Elite
EliteOP7mo ago
alr but wouldnt polling have the longest delay? also what would be the easiest to setup? websockets or server sent events? and are there docs or examples of this in nextjs?
Ian
Ian7mo ago
Long polling doesn't really have a long delay as long as there aren't many messages in rapid succession. The way long polling works is that the client sends a request to the server over http. Then the server holds the request without responding until it needs to send something. Every so often this is redone to not reach any time-out limits, but it's quite instant for all intents and purposes If your app, in general, needs a lot of interactivity, then web sockets are great. Otherwise long polling is imo the easiest to set up. Because theyre just http requests
Elite
EliteOP7mo ago
alr so paypal sends me a request on my webhook endpoint (/api/webhooks/paypal/check-invoice) after i validate all the stuff, i update the status of the document in mongodb (to "invoice paid") on the frontend, i just need it to check status again and verify that the status is "invoice paid" so it can do the logic to render whats next. how would long polling work in this scenario? so when i recieve the paypal invoice, do i update the status in mongo, then send a request to a long poll route?
Ian
Ian6mo ago
Sidenote: this would most easily be handled by a normal polling imo. Waiting 3 seconds here after payment is perfectly ok 🙃 Alternatively you could also improve simple polling by holding the request a bit on the server, and checking multiple times like so:
app.get('/check-payment-status', (req, res) => {
for (let i = 0; i < 10; i++) {
if (await isPaymentComplete()) {
return res.send("Payment complete!")
}
await new Promise(r => setTimeout(r, 500))
}
res.send('Payment not yet complete');
});
app.get('/check-payment-status', (req, res) => {
for (let i = 0; i < 10; i++) {
if (await isPaymentComplete()) {
return res.send("Payment complete!")
}
await new Promise(r => setTimeout(r, 500))
}
res.send('Payment not yet complete');
});
That way you skip the overhead of going to the server and back (which can often be an issue in serverless setups)
how would long polling work in this scenario?
Happy to help a bit here, but what does your stack look like? ^^ If you want to do long-polling you'll need state. Super simple example of long polling to explain how it works (you can definitely use a library if you want): Server:
const clients = {};

app.get("/new-events", (req, res) => {
const sendEvent = (data) => {
delete clients[req.body.id];
res.json(data);
}
clients[req.body.id] = sendEvent;
});

app.post("/send-event", (req, res) => {
const client = [req.body.id];
if (!client) {
return res.send("That user is not active");
}
client.sendEvent(req.body.data);
return res.send("Sent!")
});
const clients = {};

app.get("/new-events", (req, res) => {
const sendEvent = (data) => {
delete clients[req.body.id];
res.json(data);
}
clients[req.body.id] = sendEvent;
});

app.post("/send-event", (req, res) => {
const client = [req.body.id];
if (!client) {
return res.send("That user is not active");
}
client.sendEvent(req.body.data);
return res.send("Sent!")
});
Note: this is a simplified example and needs more work to be used in production.
Elite
EliteOP6mo ago
thank you so much. i jsut using nextjs fro both frontend and backend isnt it better to do long polling? so rn when a paypal invocie is paid, i get the event, verify it, update in mongodb, and then i need to let the poll know:
if (updateStatus) {
// todo: add long polling notify here
return NextResponse.json({ status: "success" });
} else {
return NextResponse.json({ error: "Failed to update transaction status" }, { status: 500 });
}
if (updateStatus) {
// todo: add long polling notify here
return NextResponse.json({ status: "success" });
} else {
return NextResponse.json({ error: "Failed to update transaction status" }, { status: 500 });
}
and then on my frontnend how could i make like all the stauts changes seamless and instant? rn i just hjave a useeffect for every time the status state changes, the ui gets updated so if im doing long poll, the code abovie is in a route handler (the paypal invoice paid one). what would i do to long poll hey @Ian sorry for the ping. just wondering ^^
Ian
Ian6mo ago
i jsut using nextjs fro both frontend and backend
In that case, I'm assuming all your requests are serverless, meaning you have no state on the server side (because every request lives in its own serverless function) To achieve long polling you need state, because the endpoint that receives the paypal event somehow needs to communicatie with the long polling request. Here are some options: Redis Using redis pub/sub, the endpoint that receives the paypal request can publish an event to redis, and the long polling endpoint can subscribe to events in redis. Mongo change streams I was looking around, and it seems that mongo supports "change streams" https://www.mongodb.com/docs/manual/changeStreams/ Never tried them, but perhaps they can be used by the polling endpoint to listen to events in mongo There are many other ways to achieve the same result, but realise that normal polling will still be the simplest solution. Make sure you ask yourself the question what the added benefit is of users being redirected away from the checkout page instantly, instead of having to wait 3 seconds
Elite
EliteOP6mo ago
So should I do normal polling? Or how is it gonna work. Can I not like ping my frotnned state when I get smth from the webhook on the backend
KiKo
KiKo6mo ago
If you are deployed to vercel that will not work because your backend only exists during the time a request is made. To use we sockets, server sent events, or long polling you need a server that will stay alive Simplest solution is on the front end to wait a few seconds and send a requests, "was the payment successful yet?" Over and over again until you get a success response
Elite
EliteOP6mo ago
at scale can that kill my vercel limits? im on the pro plan rn but yk just in case. ye i am, so it isnt possible for my backend endpoint to send some data over to the frontend to update
KiKo
KiKo6mo ago
Use the callback url, all on the front end, to act as the "trigger" to re-check the payment was completed Full flow... User clicks pay, fills in details on PayPal site, complete payment, PayPal sends webhook and you update your db, user is redirected back to your site, once they land on your site, query your db to see that the purchase is complete, if not try again. If you have to try more than once you can probably add a fixed delay to account for anything delays on PayPal end
Elite
EliteOP6mo ago
So rn, when the user submits the form we auto invoice them based on the details they put. Then when the invoice is paid PayPal sends a webhook. There I would update that transaction in my db, and on the frontend I constantly check with a timer for any changes of that value right
KiKo
KiKo6mo ago
You probably won't have to constantly check... I'm assuming that within a few seconds you might have a response from paypal a few seconds from the user returning to you site that is
Elite
EliteOP5mo ago
ye well it would depend on how fast the user pays the invoice. so ill probs set a max of 2 min, and then stop checking and cancel the transaction
KiKo
KiKo5mo ago
That shouldn't matter as long as you only start checking once the callback url is redirected to
Elite
EliteOP5mo ago
wdym? the flow is: user submits the form, we auto invoice their paypal. then we set a 2 min timer for the user to pay the invoice. if user pays, paypal sends us a webhook. we then update a db field. frontend continually checks updated field value in mongodb every 5 sec. if the user does not pay, (2 min goes past), and no paypal webhook sent, then we cancel the invoice and update state to show an error on frontend so basically i can't make my backend send a request to my frontend. the frontend needs to check continuously
KiKo
KiKo5mo ago
When someone clicks pay with PayPal doesn't it take them off your site to pay? They go to some PayPal.com website They click pay and get redirected back to your website once it completes There's essentially two callbacks, one client side and one server side Both these callbacks should occur in sync, unless the user is being intentionally malicious, so the timings will line up and you shouldn't have to reach out to your database too many times, id assume under 15 seconds total....
Elite
EliteOP5mo ago
no so we actually invoice them automtically with the paypal api when the form is submitted so with the flow i sent above, is my appraoch right? checking every 5 seconds for 2 min? or any better way to optimize
KiKo
KiKo5mo ago
I mean yea probably your only option when using serverless Without breaking out and spinning up a server just for this
Elite
EliteOP5mo ago
Hm ok thanks Can I not do streaming or smth like that.
KiKo
KiKo5mo ago
Your problem will be the vercel timeouts They kill lambdas after a set amount of time Depending on your payment tier, either way you get billed for the execution time I've never done a payment like the way you are describing btw. Is this B2B?
Elite
EliteOP5mo ago
kinda we have pro tier. so 300 seconds 300 seconds is enough right. 2 min max per transaction.
KiKo
KiKo5mo ago
Idk test it Maybe just let the user hit a button to refresh
Elite
EliteOP5mo ago
Hm ok
Want results from more Discord servers?
Add your server