-
-
Notifications
You must be signed in to change notification settings - Fork 33
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Cannot make handle_post to return 200 #335
Comments
Hi! The middlewares usually receives the request object directly, and internally parses the body and the required headers. However, I think you should be using the web-standard middleware. Vercel's middleware is designed for Vercel's serverless endpoints, which uses node http requests under the hood, and are nothing like Next's endpoints. I will add a middleware for NextJS in the near future, so there's no confusion and make it clear which one use. Let me know if this helps you! |
@Secreto31126 thanks for clarifying that! From what i tested, if i am using the Whatsapp.post, it does already handles the header signature verification, right? I am running on the edge, so i have an endpoint to receive whatsapp webhook posts and add it to a messaging queue:
This wa.post already verifies the header signatures as I mentioned ealier. Then on my /process-queue, i receive the data and subscribe to message again:
However it seems its not possible to wa.post() without a signature even though this is optional parameter. Returns 401 error. Regards |
The library offers a way to disable the signature verifications, you can use it by setting Documentation for the library settings here Also, if you read the source code for the web-standard middleware, you will see it does exactly the same as your first block of code, so yeah, that is the intended usage of the library. |
Thanks @Secreto31126 ! Very helpful, so i guess in my case the only workaround is to have one client with secure false, since I cannot get the original headers value from inside the on.message event. (If so i would then pass them to the queue). Otherwise I would have to enqueue all requests... |
I find your design pattern quite interesting and confusing at the same time 😄. Even if the on.message handler could receive the signature, it would become useless as the raw object re-stringified might result in a different text from the original, so the endpoint might sometimes randomly fail. I will keep in mind your use case and think of possible solutions for better compatibility with this message queuing style, which seems reasonable to support. Feel free to ask if you have any other question :) |
@Secreto31126 i know it might sounds a little confusing. But its actually very simple.
The first version of my system i built using fetch only. So i decided to give a try on this library. So i am learning form it for a couple of hours. I wish a had found it before. Nice TS work btw.
I can sucessfully obtain the mediaUrl from retrieveMedia (I can even use this url on insomnia/postman to view the image authenticated.) I had the same issue before that I solved changing User-agent. I am running on node 22. |
@Secreto31126 |
I'm actually not surprised media fetching is broken again, the method, although 5 lines long, is probably the most unstable in the library. I spent the last hour trying to debug what might be the issue. Using curl to fetch the image seems to work as expected, so I tried to replicate the headers. I mix and matched I got your update while writting the response, and seems like your solution does work. I'm quite curious to know what's the configuration at facebook's server to find out what the real issue is. I will do a patch update to include your solution, as it seems the closest to a permanent fix for the media fetching. I will also consider opening an issue at undici/fetch, maybe there's something specific we aren't considering from their side. Thanks for the input! |
So I found this interesting issue in undici/fetch which might be the reason why Facebook is throwing the error. TL;DR: Node's native fetch adds some extra headers which can't be removed. Weird. But seems to be the exact reason why it doesn't work. Here's an example of the same request using undici's const { url, mime_type } = await Whatsapp.retrieveMedia("id");
const req = await request(url, {
headers: {
Authorization: `Bearer ${TOKEN}`,
// Still had to tell which User Agent it is, otherwise I would get an empty response, idk why
"User-Agent": "curl/7.68.0",
}
});
const arr = await req.body.arrayBuffer();
fs.writeFileSync("media.jpg", Buffer.from(arr)); In conclusion, there seems to be some friction between undici's fetch and Facebook's expected headers. Based on the issue mentioned before, undici will not allow removing the default headers, and Facebook is Facebook, I doubt they really care for this API. Anyway, the patch is on the go. Thanks again for the help! |
@Secreto31126 Nice catch! You are more than welcome! If you receive, let's say, two Whatsapp.on.message events very closely to each other. Do you just send back the messages back to the user out of context/order? So this behavior would make impossible to keep these data sequential and chat logic's. Thanks again.. |
Mini rant, feel free to ignore :)
Asserting message order is probably the most annoying thing in the API. In order to make sure the messages are sent as you expect, you must send the first message (in your case, the image), get the message id from the response, wait for a message status update with A super naive pseudo code for such implementation would be: const messages_continuations: Record<string, WhatsAppMessage> = {}
function wait_id_to_send_continuation(id: string, message: Message) {
messages_continuations[id] = message;
}
const bot = "123";
const user = "456";
const { id } = await Whatsapp.sendMessage(bot, user, new Image("https://heavy-pictures.com", false, "First"));
wait_id_to_send_continuation(id, new Text("Second"));
Whatsapp.on.status(async ({ id, status }) => {
if (status === "delivered" && id in messages_continuations) {
await Whatsapp.sendMessage(bot, user, messages_continuations[id]);
}
}); As you can see, even the simplest pseudo code for order assertion gets annoying and isn't viable in serverless enviroments without persistance such as Redis. So most of the times I just don't care. So far I have convinced my "clients" that it was an edge case scenario and wasn't a big deal compared to the complexity required to make the chat look clean. That behind, I'm curious to know why you are trying to queue the whole API request rather than just what you need. For example, you could just store the message object (which includes the message type), the sender and the bot number, while on the other endpoint use the data directly rather than going through Your project seems quite interesting! I thought it was just a matter of time until someone tried to get AI in WhatsApp (looks like Meta itself is trying to do so), so I'm quite happy to hear you are using the library 😄. P.S.: Another option for clearer chat conversations can be using the |
Just released 3.0.1 to npm, if you want to check it out. Let me know if it fixes the fetching media issue. |
@Secreto31126 , I understand what you mean but this mindset works on a stateful environment. But on a stateless environment, you have to somehow trigger the interaction response. Maybe i should reevaluate if I shouldn't be doing this on a stateful environment, which wasn't my first option to worry on scalability issues and peaks. Anyways, it would facilitate a lot if we could trigger the Whatsapp.on.message manually. So I can control the response from this. Thanks for the release! |
When I said storing I meant sending less data to que qstash API, wrong word choice, my bad. wa.on.message = async ({ from, message, phoneID }) => {
if (message) {
const queue = qstash.queue({
queueName: from
});
await queue.enqueueJSON({
url: `${ENV.APP.url}/api/whatsapp/process-queue`,
body: {
userID: from,
phoneID,
message
}
});
}
wa.markAsRead(phoneID, message.id);
}; And in the other endpoint something like: import wa from "...";
export async function POST(req) {
const { userID, phoneID, message } = await req.json();
// Do stuff
await wa.sendMessage(phoneID, from, new Text("Did stuff"));
} What do you think? |
@Secreto31126 yep! This is exactly what i am doing now.
In your example itself wa.handle_post (which is wa.post under the hood) will return 200 to facebook webhook response if the message has passed (ie. signature has matched), regardless if enqueue action has succeeded. Right?
What if this fails? In the way its built today, facebook will never deliver this message again, because signature has passed and returned 200. Message would be lost.
So retry in this case would happen only if signature check fails. Which makes even less sense in my opinion. Thats exactly the case when there is no need for retry. |
That's actually true, there's no way to force the library to return an error code different than 200 on verified messages. It was designed as such because the API shouldn't be returned with an error status when the message was received correctly. If an error ocurred after the message was verified, you shouldn't rely on the API to retrigger the code... However, I do think it's a design flaw not letting the user customize the HTTP response code. I believe that the code should be flexible enough to allow the users fine tune their systems, but not so over engineered as it would become unusable. I will keep this in mind too, and see if I can think of a good solution for this scenario. |
The message being received don't necessarily means it didn't fail. I think its reasonable to give the option to the developer what to do.
Well, that is what webhooks are for (imo). Also otherwise there would not exist 500 error codes. And no service would retry to deliver a webhook.
From the experience by using other sdk's, I think you should expose a verifyRequestSignature function. And somehow let the user trigger and respond to the events. I am actualy refactoring to use my own verifyRequestSignature and response, but still use the typings and client to send over messages. |
I am running on Nextjs 14 App router, and I am able to setup the client and send messages (using provided vercel middleware), however when using the Whatsapp.handle_post(rawBody), i always get 500 returned.
Remember I am able to use the client to send messages however this handle_post is not working for some reason.
Plus: inspite the docs says need to disable bodyParser, its no longer needed on Next app router routes.
Is there anything i am missing here?
The text was updated successfully, but these errors were encountered: