# Welcome new Shopify customers by text

> Send each new Shopify customer who agreed to texts a welcome from your own number a couple of hours after their first order, with Zapier or with a webhook handler that checks Shopify’s consent and signature.

Priya runs Fern & Folly, a plant shop on Shopify. Customers who tick “Text me with news and offers” at checkout get nothing personal from her, only Shopify’s order emails. She wants each of them to get a short welcome from her own number a couple of hours after their first order: who she is, how to reach her, and that a care card for their plant is in the box.

## What you’ll have

- A welcome text from your number to each new customer who agreed to texts at checkout, two hours after their first order.
- Customers who didn’t agree left alone, and never texted twice.
- Their reply in BlueBot’s inbox and on your iPhone.

## What you need

- A BlueBot account with your Mac connected.
- A Shopify store where you can change settings.
- Checkout set to collect a phone number and offer SMS sign-up. In Shopify’s admin, open **Settings**, then **Checkout**, and under **Marketing options** turn on **SMS**. Customers then see a box to tick for texts, and Shopify records what they chose on the customer as `sms_marketing_consent`.

> **Only text people who said yes.** A welcome is marketing, so it goes only to customers whose SMS marketing consent is `subscribed`. Both ways below check that before sending. A Text Hook would text every customer Shopify posts without looking, so it isn’t the tool for this one.

## With Zapier

### Start with Shopify’s new customer trigger

In Zapier, make a Zap. For the trigger, pick **Shopify** and **New Customer**, connect your store and press **Test trigger** to load a recent customer.

### Keep only the customers who agreed

Add **Filter by Zapier**. Only continue if the customer’s SMS marketing consent state (exactly matches) `subscribed`, and the customer’s phone exists. Everyone else stops here.

### Wait two hours

Add **Delay by Zapier**, **Delay For**, 2 hours. The welcome then arrives after the order confirmation rather than on top of it.

### Send the welcome

Add **BlueBot**, **Send Text**, connected with an API key from **Developers**, then **API keys**:

| BlueBot field | Map it to                                                                                                                                                                                            |
| ------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| To            | The customer’s **Phone**.                                                                                                                                                                            |
| Text          | `Hi`, the customer’s **First Name**, then `, it’s Priya from Fern & Folly. Thanks for your first order. There’s a care card in the box, and you can text me here any time your plant looks unhappy.` |
| Metadata      | `source` set to `shopify`, and `customer_id` set to the customer’s **ID**.                                                                                                                           |

Turn the Zap on.

Shopify customers are made at checkout, so the trigger fires as the first order is placed. A customer who later buys again isn’t new and gets nothing.

## With your own handler

Use this when you want to choose what the welcome says from the order, or record it in your own systems. Shopify posts each new customer to your handler; the handler checks Shopify’s signature and the consent, then schedules the text two hours out with `send_at`.

### Add the webhook in Shopify

In Shopify’s admin, open **Settings**, then **Notifications**, scroll to **Webhooks** and press **Create webhook**. Choose the **Customer creation** event, the **JSON** format, and your handler’s address (`https://fernandfolly.example/hooks/shopify`). Shopify shows the secret it signs webhooks with on the same page. Put it in your server’s environment with your BlueBot key:

```bash
export BLUEBOT_API_KEY="bb_live_…"
export SHOPIFY_WEBHOOK_SECRET="the secret Shopify shows"
```

### Write the handler

Shopify signs each post with `X-Shopify-Hmac-Sha256`: the base64 HMAC-SHA256 of the raw body, keyed with that secret. Check it, check the consent, then schedule the welcome. The Idempotency-Key is built from the customer’s id, so Shopify sending the same customer again, as it does when your handler is slow, never texts them twice.

```ts title="TypeScript"
// app/hooks/shopify/route.ts (Next.js). Any framework works: read the raw body first.
import { createHmac, timingSafeEqual } from 'node:crypto';
import BlueBot from '@usebluebot/sdk';

const client = new BlueBot({ apiKey: process.env.BLUEBOT_API_KEY });

function signedByShopify(payload: string, header: string | null): boolean {
  if (!header) return false;
  const expected = createHmac('sha256', process.env.SHOPIFY_WEBHOOK_SECRET!).update(payload).digest('base64');
  return header.length === expected.length && timingSafeEqual(Buffer.from(header), Buffer.from(expected));
}

export async function POST(request: Request) {
  const payload = await request.text();
  if (!signedByShopify(payload, request.headers.get('x-shopify-hmac-sha256'))) {
    return new Response('Bad signature', { status: 401 });
  }

  const customer = JSON.parse(payload);
  const agreed = customer.sms_marketing_consent?.state === 'subscribed';
  if (!agreed || !customer.phone) return new Response(null, { status: 200 });

  const twoHoursFromNow = new Date(Date.now() + 2 * 60 * 60 * 1000).toISOString();
  await client.messages.send(
    {
      to: customer.phone,
      text: `Hi ${customer.first_name ?? 'there'}, it’s Priya from Fern & Folly. Thanks for your first order. There’s a care card in the box, and you can text me here any time your plant looks unhappy.`,
      sendAt: twoHoursFromNow,
      metadata: { source: 'shopify', customer_id: String(customer.id) },
    },
    { idempotencyKey: `shopify-welcome-${customer.id}` },
  );
  return new Response(null, { status: 200 });
}
```

```python title="Python"
# Flask. Any framework works: read the raw body first.
import base64
import hashlib
import hmac
import json
import os
from datetime import datetime, timedelta, timezone

from flask import Flask, abort, request
from bluebot import BlueBot

app = Flask(__name__)
client = BlueBot(api_key=os.environ["BLUEBOT_API_KEY"])

def signed_by_shopify(payload: bytes, header: str | None) -> bool:
    if not header:
        return False
    digest = hmac.new(os.environ["SHOPIFY_WEBHOOK_SECRET"].encode(), payload, hashlib.sha256).digest()
    return hmac.compare_digest(header, base64.b64encode(digest).decode())

@app.post("/hooks/shopify")
def new_customer():
    payload = request.get_data()
    if not signed_by_shopify(payload, request.headers.get("X-Shopify-Hmac-Sha256")):
        abort(401)

    customer = json.loads(payload)
    agreed = (customer.get("sms_marketing_consent") or {}).get("state") == "subscribed"
    if not agreed or not customer.get("phone"):
        return "", 200

    client.messages.send(
        to=customer["phone"],
        text=f"Hi {customer.get('first_name') or 'there'}, it’s Priya from Fern & Folly. Thanks for your first order. "
        "There’s a care card in the box, and you can text me here any time your plant looks unhappy.",
        send_at=(datetime.now(timezone.utc) + timedelta(hours=2)).isoformat(),
        metadata={"source": "shopify", "customer_id": str(customer["id"])},
        idempotency_key=f"shopify-welcome-{customer['id']}",
    )
    return "", 200
```

The handler answers Shopify quickly, since the text is scheduled rather than sent while Shopify waits. Underneath, the call is this request:

```bash
curl -X POST "https://usebluebot.com/api/v1/messages" \
  -H "Authorization: Bearer $BLUEBOT_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: shopify-welcome-7290918453304" \
  -d '{
    "to": "+15555550123",
    "text": "Hi Maria, it’s Priya from Fern & Folly. Thanks for your first order. There’s a care card in the box, and you can text me here any time your plant looks unhappy.",
    "send_at": "2026-11-13T21:41:00.000Z",
    "metadata": {
      "source": "shopify",
      "customer_id": "7290918453304"
    }
  }'
```

```ts
import BlueBot from '@usebluebot/sdk';

const client = new BlueBot({ apiKey: process.env.BLUEBOT_API_KEY });

const message = await client.messages.send({
  to: '+15555550123',
  text: 'Hi Maria, it’s Priya from Fern & Folly. Thanks for your first order. There’s a care card in the box, and you can text me here any time your plant looks unhappy.',
  sendAt: '2026-11-13T21:41:00.000Z',
  metadata: {
    source: 'shopify',
    customer_id: '7290918453304',
  },
}, { idempotencyKey: 'shopify-welcome-7290918453304' });
console.log(message);
```

```python
import os
from bluebot import BlueBot

client = BlueBot(api_key=os.environ["BLUEBOT_API_KEY"])

message = client.messages.send(
    to="+15555550123",
    text="Hi Maria, it’s Priya from Fern & Folly. Thanks for your first order. There’s a care card in the box, and you can text me here any time your plant looks unhappy.",
    send_at="2026-11-13T21:41:00.000Z",
    metadata={
        "source": "shopify",
        "customer_id": "7290918453304",
    },
    idempotency_key="shopify-welcome-7290918453304",
)
print(message)
```

The answer is the text with `state: "scheduled"`. Until it’s due you can change its words or time with `PATCH /v1/messages/{id}`, or cancel it.

## Test it

Place a test order in your own store as a new customer, with your own mobile number, and tick the box for texts. Use a discount code or a test payment gateway so nothing is charged. To see it sooner, set the delay to two minutes while you test, then put it back.

| You got                                                                   | It means                                                                                 | Do next                                                                                                   |
| ------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------- |
| A text on your phone from your own number after the delay                 | It works.                                                                                | Put the delay back to two hours and turn it on.                                                           |
| The text in **Messages** with `state: "scheduled"`                        | It’s waiting for its time.                                                               | Wait, or cancel it there and test again with a shorter delay.                                             |
| Nothing in **Messages**, and the Zap’s history says the filter stopped it | Shopify didn’t record your consent.                                                      | Check the box at checkout was ticked, and that the customer in Shopify shows SMS marketing as subscribed. |
| `401` in Shopify’s webhook deliveries                                     | Your handler’s signature check failed.                                                   | Check the secret, and that you hash the raw body before parsing it.                                       |
| The text failed with `failure.reason: "messages_refused"`                 | Messages on your Mac wouldn’t send it, usually a regular text with no iPhone forwarding. | See [Services and SMS fallback](https://usebluebot.com/docs/concepts/services).                                                 |

## When it goes wrong

- **A customer gave their number only for shipping.** Shopify keeps a shipping phone on the address and a contact phone on the customer. The handler reads the customer’s `phone`, which is the one consent applies to. Leave shipping numbers alone.
- **Your Mac was off when the text was due.** The welcome waits for your Mac and goes when it’s back, unless it’s still waiting 24 hours after its time, when it expires. A welcome two days late is worse than none, so that default suits it.
- **A busy sale weekend.** More than 50 first-time customers in a day goes past your daily limit for new people, and the rest wait until tomorrow with `hold.reason: "daily_new_people_limit"`. Raise it in **Settings**, then **Sending rules**, only as far as you’re sure those people want the text.
- **A customer replies STOP.** They go on your stop list, and BlueBot cancels every text to them from then on, scheduled ones included. Shopify isn’t told; if you also send texts from Shopify, unsubscribe them there too.

## Next

- [Shopify recipes](https://usebluebot.com/docs/recipes/shopify): Order updates, abandoned checkouts and more through Zapier.
- [Scheduled sends](https://usebluebot.com/docs/sending/scheduled): `send_at`, changing a scheduled text, and when it expires.
- [Follow up until they answer](https://usebluebot.com/docs/guides/drip-sequence): Send more than one text and stop when they reply.
- [Stop words](https://usebluebot.com/docs/concepts/stop-words): What happens when somebody texts STOP.
- [Idempotency](https://usebluebot.com/docs/concepts/idempotency): Why the customer’s id goes in the key.
- [Zapier](https://usebluebot.com/docs/zapier): BlueBot’s Zapier app, action by action.
