Skip to main content

Command Palette

Search for a command to run...

Build a secure Wix Forms to WhatsApp handoff with Velo

Updated
4 min read

A common lead-capture pattern is simple on paper: a visitor submits a form, and the team follows up on WhatsApp while the intent is still fresh. The part worth getting right is the boundary between the browser, your Wix backend, and the messaging provider.

I used Zaple's Wix Forms integration guide as the starting point for this build note, then tightened the flow into a safer Velo pattern that keeps credentials out of the page and makes the integration easier to debug.

The shape of the integration

The architecture I prefer has four small steps:

  1. Wix Forms V2 stores the visitor submission.

  2. The page code listens for onSubmitSuccess(), so the automation only runs after Wix has accepted the form.

  3. The page sends a minimal payload to a backend web module.

  4. The backend reads secrets from Wix Secrets Manager and calls the WhatsApp template API.

That separation matters. The browser can collect form state, but it should not know your API key, API secret, template IDs you want to protect, or provider-specific request headers.

Store secrets in Wix first

Create secrets such as:

Zaple_API_Key
Zaple_API_Secret

Keep these names boring and explicit. Future you will thank present you when debugging a production form at 11 PM.

Page code: collect only what the backend needs

The frontend should do as little as possible: wait for a successful submission, read the field values, normalize the phone number, and call the backend method.

import { sendWhatsAppFollowup } from 'backend/zaple.web';

$w.onReady(function () {
  $w('#leadForm').onSubmitSuccess(async () => {
    const values = $w('#leadForm').getFieldValues();

    const firstName = String(values.first_name || '').trim();
    const phoneNumber = String(values.whatsapp_number || '').replace(/\D/g, '');

    if (!phoneNumber) {
      console.warn('Skipping WhatsApp follow-up: missing phone number');
      return;
    }

    await sendWhatsAppFollowup({
      firstName,
      phoneNumber
    });
  });
});

Two details are easy to miss here. First, the field keys must match the keys returned by getFieldValues(). Second, strip formatting from phone numbers before passing them to the backend.

Backend module: call the provider from the server side

Create a backend file such as backend/zaple.web.js. The backend can safely read secrets and call the API.

import { Permissions, webMethod } from 'wix-web-module';
import { fetch } from 'wix-fetch';
import { getSecret } from 'wix-secrets-backend';

export const sendWhatsAppFollowup = webMethod(
  Permissions.Anyone,
  async ({ firstName, phoneNumber }) => {
    if (!/^\d{8,15}$/.test(phoneNumber)) {
      throw new Error('Invalid phone number');
    }

    const apiKey = await getSecret('Zaple_API_Key');
    const apiSecret = await getSecret('Zaple_API_Secret');

    const payload = {
      template_id: 'YOUR_APPROVED_TEMPLATE_ID',
      country_code: '91',
      send_to: phoneNumber,
      body_text1: firstName || 'there'
    };

    const response = await fetch('https://app.zaple.ai/api/v3/send-template-message', {
      method: 'POST',
      headers: {
        'Zaple-Api-Key': apiKey,
        'Zaple-Api-Secret': apiSecret,
        'Content-Type': 'application/json'
      },
      body: JSON.stringify(payload)
    });

    const text = await response.text();

    if (!response.ok) {
      throw new Error('WhatsApp provider error ' + response.status + ': ' + text);
    }

    return { ok: true };
  }
);

This keeps the credentials behind Wix's backend boundary. It also means frontend users cannot inspect the page source and copy the provider headers.

The security part people skip

Permissions.Anyone is sometimes necessary because the visitor submitting the public form is not logged in. But it also means the backend method is callable from a public page, so do not treat it as private just because it lives in backend/.

For production, add at least three controls:

  • Validate the phone number, template inputs, and allowed country codes.

  • Add rate limiting, CAPTCHA, or another abuse-control layer before the backend call.

  • Avoid logging full phone numbers, API responses containing personal data, or secret values.

A useful rule: logs should help you debug the integration without becoming a second database of customer contact details.

Testing checklist

Before shipping the automation, I test these cases:

  • Submit a valid form and confirm the backend receives the expected field keys.

  • Submit a phone number with spaces and punctuation.

  • Submit an empty phone number and confirm no API call is made.

  • Use an invalid template ID and confirm the backend surfaces the provider error.

  • Review Wix logs to make sure secrets and full phone numbers are not printed.

Why this pattern works

The important idea is not the specific provider call. It is the handoff: browser code captures intent, backend code owns secrets, and the messaging API receives a clean, minimal payload.

That same pattern works for lead forms, event registrations, demo requests, and quote forms. Keep the frontend thin, keep credentials server-side, and make the failure states obvious before real users depend on the workflow.