· 2 min · TextMeFlow Team

Sending WhatsApp Messages from Klaviyo Flows

Klaviyo flows already know exactly when a customer abandoned their cart, placed an order, or went quiet for 60 days — that's the whole point of flow triggers. The messages themselves just go out as email or, on higher plans, Klaviyo SMS. Neither gets opened as fast as WhatsApp. If your store's customers are in a market where WhatsApp is the default channel, routing a flow step to WhatsApp instead of (or alongside) email is a small addition: one webhook action and a receiver that calls the TextMeFlow messages API.

This is a different integration from wiring Shopify or WooCommerce order webhooks directly — here Klaviyo is already doing the segmentation and timing (wait steps, flow filters, A/B splits), and you're just giving it a WhatsApp exit instead of only email/SMS.

Step 1: Add a webhook action to the flow

In Klaviyo, open the flow (Abandoned Cart, Order Confirmation, Win-back, whatever triggers it) and add a Webhook action at the point where you want the WhatsApp message sent — usually replacing or running parallel to an email step. Klaviyo's webhook action lets you:

  • Set the endpoint URL (your receiver)
  • Choose POST and JSON body
  • Map profile and event properties into the payload with Klaviyo's templating syntax

Build the payload with just what you need:

{
  "phone": "{{ person|lookup:'phone_number' }}",
  "first_name": "{{ person|lookup:'first_name' }}",
  "cart_url": "{{ event|lookup:'$ecommerce.abandoned_checkout_url' }}",
  "cart_total": "{{ event|lookup:'$value' }}"
}

The exact property names depend on which event triggers the flow — check the "Preview" panel in Klaviyo's webhook step to see the real values before going live.

Step 2: Secure the webhook

Klaviyo lets you add custom headers to the webhook action. Add a static bearer token:

Authorization: Bearer <a long random secret you generate yourself>

Your receiver checks that header on every request and rejects anything that doesn't match — this is the only thing stopping someone else from POSTing fake orders at your endpoint, since Klaviyo itself doesn't sign these payloads.

Step 3: Receiver that calls TextMeFlow

<?php
$auth = $_SERVER['HTTP_AUTHORIZATION'] ?? '';
if ($auth !== 'Bearer ' . getenv('KLAVIYO_WEBHOOK_SECRET')) {
    http_response_code(401);
    exit;
}

$data = json_decode(file_get_contents('php://input'), true);
$phone = $data['phone'] ?? null;

if (!$phone || !preg_match('/^\+[1-9]\d{6,14}$/', $phone)) {
    http_response_code(200); // nothing to retry, just skip
    exit;
}

$text = sprintf(
    "Hi %s, you left something in your cart (%s). Finish your order: %s",
    $data['first_name'] ?? 'there',
    $data['cart_total'] ?? '',
    $data['cart_url'] ?? ''
);

$ch = curl_init('https://api.textmeflow.eu/v1/messages');
curl_setopt_array($ch, [
    CURLOPT_POST => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('TEXTMEFLOW_TOKEN'),
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode(['to' => $phone, 'text' => $text]),
    CURLOPT_RETURNTRANSFER => true,
]);
curl_exec($ch);
http_response_code(200);

Same shape in Node or Python — see the PHP, Python and Node examples if you want those directly.

Step 4: Phone number format is the main failure point

Klaviyo profiles store phone numbers however the checkout or signup form collected them — sometimes with a leading 0 instead of a country code, sometimes with spaces or dashes. TextMeFlow requires strict E.164 (+3247...); anything else comes back as 400 invalid_recipient. Don't try to guess a country code in your receiver — skip the send when the number doesn't already match + plus digits, and fix the collection form (or Klaviyo's profile property mapping) instead of papering over it downstream.

Step 5: Don't duplicate what the flow already sends

If the flow still emails too, decide once per flow which channel is primary rather than sending both every time. A common pattern: WhatsApp first (fastest open), with the original email step moved later in the flow as a fallback only for profiles missing a valid phone number — use a flow filter on the phone property to branch.

Mind the opt-in and quiet hours

A Klaviyo profile consenting to email or SMS marketing is not automatically consent for WhatsApp — check what your opt-in form actually asked for before wiring flow messages at any volume (proof of opt-in covers what to keep on file). TextMeFlow also won't deliver outside your account's configured quiet hours by default — a win-back flow that fires at 2am gets queued and sent at a sane hour instead, which is one less thing to build into the flow logic yourself.

Start free on TextMeFlow — 50 messages a month, no time limit, enough to wire one Klaviyo flow end to end and see real sends before deciding on a paid plan.

Zelf WhatsApp-berichten versturen via API?

Gratis voor altijd tot 50 berichten/maand. QR scannen en binnen 5 minuten verstuur je je eerste bericht.

Gratis voor altijd