Skip to content
Node.js

Building a Reliable WhatsApp Join Request Queue with Node.js

Explore a robust system for managing WhatsApp group join requests using Node.js, ensuring durable moderation and reliable processing.

Topic
Node.js
Reading time
4 min
Length
910 words
Published
Sep 2, 2026
03:24 pm IST
In this article
  1. Introduction
  2. Webhook-Triggered System
  3. Webhook Verification and Handling
  4. Ensuring Durable Infrastructure
  5. Fetching and Persisting Requests
  6. Moderation and Approval Workflow
  7. Reconciliation and Polling
  8. Handling Missing Requests
  9. Designing the Moderator Workflow

Introduction

Managing WhatsApp group join requests efficiently is key to smooth moderation. Right now, a webhook alerts your backend of a join request. But depending only on webhooks for approvals isn't reliable—they can be late, missed, or arrive out of order. To create a more reliable system, we need to fetch, store, and reconcile join requests, keeping a durable queue with Node.js. Here, I'll show how you can set up a solid system using the UnifyPort API and Node.js to handle these requests well.

Webhook-Triggered System

The first step is receiving a webhook notification when a join request comes in. The webhook wakes up the system but shouldn't be used by itself for approving requests. Here’s how it should work:

  • Get and verify the webhook.
  • Queue a reconciliation job.
  • Fetch pending requests through the UnifyPort API.
  • Store requester IDs for moderation.
  • Approve or reject based on policy or moderator choice.
  • Reconcile the state again to keep it consistent.

This method keeps the system updated with the latest requests and handles situations like missed webhooks. Even if a webhook is delayed or doesn't reach you, fetching the current state from the list endpoint ensures you have the right information.

Webhook Verification and Handling

To maintain security, verifying webhook signatures against the raw HTTP request body is a must. This blocks replay attacks and confirms the webhook is from a trusted source. A simple Express route could look like this:

app.post(
  "/webhook",
  express.raw({ type: "application/json" }),
  async (req, res) => {
    const rawBody = req.body;
    if (!verifyWebhookSignature(req.headers, rawBody)) {
      return res.status(401).end();
    }
    const event = JSON.parse(rawBody.toString("utf8"));
    if (event.type !== "group.join_request") {
      return res.status(200).end();
    }
    await handleJoinRequestSignal(event);
    return res.status(200).end();
  },
);

Keep signature verification separate and test it using the specified header and signing contract for your webhook provider. This way, any changes in the webhook format or signing process won't disrupt your system.

Ensuring Durable Infrastructure

A local Map or array is fine for development, but it falls short in production due to its volatility. If the process restarts, your state is lost. Instead, choose more durable solutions like:

  • A database-backed jobs table.
  • Redis with a persistent queue library.
  • Amazon SQS or Google Cloud Tasks.
  • RabbitMQ or another solid queue system.

These options ensure that acknowledging the webhook doesn't erase essential reconciliation work. For example, Redis with a persistent queue library can manage retries and make sure tasks are processed exactly once, even if there are failures or restarts.

Fetching and Persisting Requests

After verifying the webhook, fetch the list of pending requests using the UnifyPort API:

async function listGroupJoinRequests({ accountId, groupId }) {
  const query = new URLSearchParams({
    group_id: groupId,
  });
  const response = await fetch(
    `https://api.unifyport.ai/v1/accounts/${encodeURIComponent(accountId)}/groups/join-requests?${query}`,
    {
      headers: {
        "X-Api-Key": process.env.UNIFYPORT_API_KEY,
      },
    },
  );
  if (!response.ok) {
    throw new Error(`Unable to list join requests: ${response.status}`);
  }
  return response.json();
}

Each request should be saved using a stable uniqueness rule like account_id + group_id + member_id. This way, you have an accurate record of pending requests. Using display names as identifiers is not recommended since they can change and aren't unique. Stick to the unique IDs the API provides.

Moderation and Approval Workflow

Handle moderation separately from the webhook receiver to allow for policy review and auditing. A simple state machine can help guide smooth transitions and prevent multiple moderators from acting at once:

pending
   ├── approving
   │      ├── approved
   │      └── pending
   └── rejecting
          ├── rejected
          └── pending

To approve a request, use this pattern:

async function updateJoinRequests({ accountId, groupId, action, memberIds }) {
  if (!["approve", "reject"].includes(action)) {
    throw new Error("action must be approve or reject");
  }
  if (!Array.isArray(memberIds) || memberIds.length === 0) {
    throw new Error("memberIds must contain at least one requester ID");
  }
  const response = await fetch(
    `https://api.unifyport.ai/v1/accounts/${encodeURIComponent(accountId)}/groups/join-requests/update`,
    {
      method: "POST",
      headers: {
        "X-Api-Key": process.env.UNIFYPORT_API_KEY,
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        group_id: groupId,
        action,
        member_ids: memberIds,
      }),
    },
  );
  if (!response.ok) {
    throw new Error(`Unable to update join requests: ${response.status}`);
  }
  return response.json();
}

Make sure all actions are logged for audits, and never log sensitive info like API keys. Logging is critical for tracking decisions and understanding how requests flow through your system.

Reconciliation and Polling

After making a decision, queue a reconciliation job to verify the group's state. Regular polling complements webhooks by filling delivery gaps. This involves immediate reconciliation when receiving a webhook and periodic checks for accuracy, usually every few minutes.

This strategy keeps the system’s state consistent, even if a webhook is missed or a request’s status changes outside your app. I've found that running a reconciliation job every 5 to 10 minutes balances performance and accuracy without overloading the server.

Handling Missing Requests

Take care with requests missing from later lists. Their absence might mean they were:

  • Approved somewhere else.
  • Rejected elsewhere.
  • Cancelled by the requester.
  • Expired or timed out.
  • Already handled by another moderator.

Avoid automatically marking every missing record as rejected unless the provider’s contract clearly supports that. A neutral state like no_longer_pending is safer until you have more information. This helps prevent mistaken rejections and keeps the moderation process on track.

Designing the Moderator Workflow

The moderator UI should show enough information for decisions without revealing unnecessary personal data. Useful fields include:

  • Group information.
  • Requester ID.
  • Phone number, if provided and necessary.
  • Request time.
  • Request method.
  • Current status.
  • Assigned moderator.

This setup ensures moderators have the context needed for informed decisions while respecting user privacy. A well-crafted UI can significantly boost the efficiency and precision of the moderation process.

Sources

Build a Reliable WhatsApp Group Join-Request Approval Queue with Node.js

Every claim above was checked against this source before publishing. The analysis, the code and the opinions are mine.

Frequently asked

Why not rely solely on webhooks for approval?

Webhooks can be delayed or missed, leading to unreliable moderation. Incorporating reconciliation and durable queues ensures accuracy.

What infrastructure should be used for queues?

Durable infrastructure like Redis, Amazon SQS, Google Cloud Tasks, or a database-backed jobs table should be used instead of in-memory solutions.

How do I manage API keys securely?

Keep API keys on the server and never expose them to client-side code. Refer to our article on securing API keys for more details.

Deepak Kumar

Written by

Deepak Kumar

Sr Software Engineer at India Today Group | Aaj Tak · MERN Stack · Generative AI

I build production web applications and Generative AI systems — React and Next.js on the front, Node.js and RAG pipelines behind them. I write here about what those systems actually do once real traffic hits them.

Message me