Ad Code

How to: Build a WhatsApp E-commerce Flow - Part 3

How to: Build a WhatsApp E-commerce Flow - Postman Collection Ready for WhatsApp Commerce Part 3

Below is a copy‑paste ready Postman collection (JSON) containing the core requests your engineers need to run end‑to‑end tests: send product list, send single product, webhook verification, create order, create checkout session, payment callback verification, and catalog sync. Import this JSON into Postman (File → Import → Raw text / File) and replace the environment variables at the top ({{PHONE_NUMBER_ID}}, {{ACCESS_TOKEN}}, {{BASE_URL}}, {{STRIPE_SECRET}}, {{STRIPE_WEBHOOK_SECRET}}, {{CATALOG_ID}}).

Notes

  • The collection uses https://graph.facebook.com/v18.0 for WhatsApp Cloud API endpoints.
  • {{BASE_URL}} is your backend (e.g., https://api.yourdomain.com).
  • The checkout example uses Stripe Checkout; adapt to your PSP as needed.
{
  "info": {
    "name": "WhatsApp Commerce - Core Collection",
    "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json",
    "description": "Core requests for product_list, product message, webhook verification, order creation, checkout session, payment callback, and catalog sync."
  },
  "item": [
    {
      "name": "Send Product List (interactive product_list)",
      "request": {
        "method": "POST",
        "header": [
          { "key": "Authorization", "value": "Bearer {{ACCESS_TOKEN}}" },
          { "key": "Content-Type", "value": "application/json" }
        ],
        "url": {
          "raw": "https://graph.facebook.com/v18.0/{{PHONE_NUMBER_ID}}/messages",
          "protocol": "https",
          "host": ["graph","facebook","com"],
          "path": ["v18.0","{{PHONE_NUMBER_ID}}","messages"]
        },
        "body": {
          "mode": "raw",
          "raw": "{\n  \"messaging_product\":\"whatsapp\",\n  \"to\":\"{{TO_WA}}\",\n  \"type\":\"interactive\",\n  \"interactive\":{\n    \"type\":\"product_list\",\n    \"header\":{\"type\":\"text\",\"text\":\"Featured products\"},\n    \"body\":{\"text\":\"Tap a product to view details\"},\n    \"footer\":{\"text\":\"Free shipping over RM100\"},\n    \"action\":{\n      \"catalog_id\":\"{{CATALOG_ID}}\",\n      \"sections\":[\n        {\"title\":\"Featured\",\"product_items\":[{\"product_retailer_id\":\"SKU123\"},{\"product_retailer_id\":\"SKU456\"}]}\n      ]\n    }\n  }\n}"
        }
      }
    }
  ]
}

Webhook Handler Pseudocode for Production

Below is copy‑paste ready pseudocode (Node.js / Express style) that covers: signature verification, idempotency, reservation TTL, order creation, payment verification, and retries. Adapt to your language/framework.

// Express style pseudocode
const express = require('express');
const crypto = require('crypto');
const bodyParser = require('body-parser');

const app = express();
// raw body needed for signature verification (Stripe style)
app.use(bodyParser.json({
  verify: (req, res, buf) => { req.rawBody = buf; }
}));

// Utility: idempotency store (Redis recommended)
async function isDuplicateEvent(eventId) {
  // return true if eventId already processed
}

// Utility: mark event processed
async function markEventProcessed(eventId, ttlSeconds = 86400) {
  // store eventId in Redis with TTL
}

// Utility: verify WhatsApp webhook signature (if using X-Hub-Signature)
function verifyWhatsAppSignature(req, appSecret) {
  const signature =
    req.headers['x-hub-signature-256'] ||
    req.headers['x-hub-signature'];
  if (!signature) return false;

  const expected =
    'sha256=' +
    crypto
      .createHmac('sha256', appSecret)
      .update(req.rawBody)
      .digest('hex');

  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expected)
  );
}

// Utility: verify PSP signature (Stripe example)
function verifyStripeSignature(req, stripeWebhookSecret) {
  const sigHeader = req.headers['stripe-signature'];
  // use Stripe SDK to verify; pseudocode:
  // return stripe.webhooks.constructEvent(req.rawBody, sigHeader, stripeWebhookSecret);
}

app.get('/webhook', (req, res) => {
  // WhatsApp verification endpoint
  const mode = req.query['hub.mode'];
  const token = req.query['hub.verify_token'];
  const challenge = req.query['hub.challenge'];

  if (mode === 'subscribe' && token === process.env.VERIFY_TOKEN) {
    return res.status(200).send(challenge);
  }
  return res.sendStatus(403);
});

app.post('/webhook', async (req, res) => {
  // Verify signature if configured
  if (process.env.WA_APP_SECRET) {
    if (!verifyWhatsAppSignature(req, process.env.WA_APP_SECRET)) {
      return res.status(401).send('Invalid signature');
    }
  }

  const body = req.body;

  for (const entry of body.entry || []) {
    for (const change of entry.changes || []) {
      const value = change.value || {};

      if (value.messages) {
        for (const msg of value.messages) {
          const eventId = msg.id || `${entry.id}:${msg.id}`;
          if (await isDuplicateEvent(eventId)) continue;
          await markEventProcessed(eventId);

          if (msg.type === 'text') {
            const phone = msg.from;
            await handleIncomingText(phone, msg);
          } else if (msg.type === 'interactive') {
            const interactive = msg.interactive;
            if (
              interactive.type === 'product_list_selection' ||
              interactive.type === 'product'
            ) {
              const retailerId =
                interactive.product_retailer_id ||
                interactive.list_reply?.id;

              await createProvisionalOrder({
                phone: msg.from,
                sku: retailerId,
                source: 'whatsapp',
                wa_message_id: msg.id
              });
            }
          }
        }
      }

      if (value.statuses) {
        // handle delivery/read receipts
      }

      if (value.payments) {
        // handle payment events
      }
    }
  }

  res.sendStatus(200);
});

app.post('/create-order', async (req, res) => {
  const { customer_phone, items, source, metadata } = req.body;

  const order = await createOrderInDB({
    customer_phone,
    items,
    source,
    metadata
  });

  await createReservations(order.id, items, 10 * 60);

  const session = await createStripeCheckoutSession(order);

  return res.json({
    order_id: order.id,
    checkout_url: session.url
  });
});

app.post('/payment-callback', async (req, res) => {
  try {
    const event = verifyStripeSignature(
      req,
      process.env.STRIPE_WEBHOOK_SECRET
    );

    const eventId = event.id;
    if (await isDuplicateEvent(eventId)) return res.sendStatus(200);
    await markEventProcessed(eventId);

    if (event.type === 'checkout.session.completed') {
      const session = event.data.object;
      const orderId = session.metadata.order_id;

      await markOrderPaid(orderId, session);
      await sendOrderConfirmationWhatsApp(orderId);
    }

    res.sendStatus(200);
  } catch (err) {
    console.error('Webhook verification failed', err);
    return res.status(400).send('Webhook verification failed');
  }
});

async function reservationCleanupJob() {
  // cleanup expired reservations
}

async function createProvisionalOrder({
  phone,
  sku,
  qty = 1,
  source,
  wa_message_id
}) {
  // transactional order + stock reservation
}

app.listen(process.env.PORT || 3000);

Implementation Notes and Best Practices

Idempotency: use event IDs (WhatsApp wamid.*, PSP event IDs) stored in Redis with TTL to avoid double processing.

  • Reservations: reserve stock with a short TTL (5–15 minutes) during checkout; if payment not completed, release automatically.
  • Signature verification: always verify webhook signatures from WhatsApp and PSPs; keep raw request body for verification.
  • Retries and dead letter: log failed webhook deliveries and implement exponential backoff; push persistent failures to a dead‑letter queue for manual review.
  • Logging: store message IDs, timestamps, and payloads for reconciliation and dispute resolution.
  • Template usage: minimize template messages; use session messages for replies within 24 hours of user message to reduce template dependency.

Quick Checklist to Run the Collection and Test End to End

  • Set environment variables in Postman: ACCESS_TOKEN, PHONE_NUMBER_ID, CATALOG_ID, BASE_URL, VERIFY_TOKEN, STRIPE_SECRET, STRIPE_WEBHOOK_SECRET, TO_WA.
  • Import collection into Postman and run requests in this order for a test:
  • Webhook Verification (GET) — ensure your webhook responds with the challenge.
    1. Webhook Receiver Test (POST) — simulate incoming message and confirm your backend handles it.
    2. Send Product List — send interactive product list to a test number.
    3. Create Provisional Order — create an order and confirm reservation created.
    4. Create Checkout Session — create a checkout session and open session.url.
    5. Payment Callback Test — simulate PSP webhook and confirm order marked paid and WhatsApp confirmation sent.

Next is to generate the exact webhook handler code in your preferred language (Node.js/Express, Python/Flask, or Go) with full dependency imports, environment variable handling, Redis examples for idempotency, and a runnable local test harness. refer to How to: Build a WhatsApp E-commerce Flow - Node.js webhook handler — ready to run (Express) with Netlify and cPanel deployment notes


Post a Comment

0 Comments