# Abandoned cart texts

Source: https://jointextkit.com/docs/abandoned-cart
Index: https://jointextkit.com/llms.txt

A shopper starts checkout and leaves. An hour later, if no order came in,
they get one text with their cart link:

```
checkout_started
  -> Wait until order_placed
       arrives: end
       not in 1 hour: continue
  -> Send SMS
```

```
Acme: you left something in your cart. Finish your order here:
https://acme.com/checkouts/c/4b1e... Reply STOP to opt out.
```

This flow is the **Abandoned cart** template in
[Console → Automations](/console/automations). It runs on two
[tracked events](/docs/automations#track-an-event) from your store:

| Event | When | Properties |
| --- | --- | --- |
| `checkout_started` | A shopper with a phone number reaches checkout | `checkout_url` (the link in the text) |
| `order_placed` | The order goes through | none needed |

A connected Shopify store sends them for you. Otherwise send them from
Shopify Flow, Klaviyo, or your own code. Each way is below.

## Before you start

- **A number that can text the public:** a registered local number or a
  verified toll-free number. See [Compliance](/docs/compliance).
- **Opt-ins on file.** A cart reminder is a marketing text, so it goes
  only to shoppers whose opt-in is recorded with TextKit. Shoppers who
  tick the SMS box at your Shopify checkout come in when you connect Shopify
  in [Console → Contacts → Integrations](/console/contacts/integrations).
  Subscribers from another tool come in as a CSV with consent columns
  ([Contacts](/docs/contacts)). Everyone else is skipped, and the run logs why.
- **An API key** for the tool that calls TextKit. A
  [restricted key](/docs/authentication) is enough: the `automations`
  scope for `POST /v1/track`, or `messages:send` for `POST /v1/messages`.

## Connected Shopify store

1. Connect the store in
   [Console → Contacts → Integrations](/console/contacts/integrations). The
   store's app needs the `read_customers` and `read_orders` scopes. A
   store connected before cart reminders existed: reconnect it once.
2. In Console → Automations, create a flow from the **Abandoned cart**
   template, pick your sending number, edit the text, and activate it.

That's all. Every 10 minutes TextKit checks the store for abandoned
checkouts and new orders and sends the two events, each checkout once.
Shoppers who ticked the SMS box at checkout are recorded as opted in on the
way. A checkout becomes an event only for a shopper who agreed to texts
(that box, or an opt-in already on file); nobody else gets a reminder. The checks run only while a flow that starts on `checkout_started`
is active. If Shopify refuses them, the integration's card in the console
says why.

## Shopify Flow

Shopify Flow is Shopify's free automation app. Its **Send HTTP request**
action is on the Grow, Advanced and Plus plans, not Basic.

1. In Console → Automations, create a flow from the **Abandoned cart**
   template, pick your sending number, edit the text, and activate it.
2. In Shopify admin, open **Flow** → **Create workflow**.
3. Trigger: **Customer abandons checkout**.
4. Action: **Send HTTP request**.
   - Method: `POST`
   - URL: `https://api.jointextkit.com/v1/track`
   - Headers: `Authorization`: `Bearer YOUR_KEY` (store the key as a Flow
     secret), and `Content-Type`: `application/json`
   - Body:

```liquid
{
  "event": "checkout_started",
  "phone": "{{ customer.phone }}",
  "properties": {
    "checkout_url": "{{ abandonment.abandonedCheckoutPayload.abandonedCheckoutUrl }}"
  }
}
```

5. Turn the workflow on.

Pick the two variables from Flow's variable list if your store's names
differ. Shopify decides when a checkout counts as abandoned, so the text
goes out about an hour after that. To stop a text for a shopper who orders
in that hour, add a second workflow: trigger **Order created**, the same
request with `"event": "order_placed"` and `"phone": "{{ order.phone }}"`.

## Klaviyo

Klaviyo's own **Abandoned Checkout** flow decides when to send and skips
shoppers who ordered. TextKit sends the text, so you don't need the
template here.

1. Open the flow that starts on **Checkout Started**.
2. After its time delay, add a **Webhook** action:
   - URL: `https://api.jointextkit.com/v1/messages`
   - Headers: `Authorization`: `Bearer YOUR_KEY`, and `Content-Type`:
     `application/json`
   - Body:

```json
{
  "from": "+18335550100",
  "to": "{{ person.phone_number }}",
  "body": "Acme: you left something in your cart. Finish your order here: {{ event.extra.checkout_url }} Reply STOP to opt out.",
  "category": "marketing"
}
```

3. Set the action to **Live**.

Klaviyo needs two-step authentication on your login before it sends
webhooks. Open **View profile and event variables** in the webhook editor
to check the checkout link's name in your account.

## Your own code

Send the two events when they happen:

```bash
curl -X POST https://api.jointextkit.com/v1/track \
  -H "Authorization: Bearer $TEXTKIT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"event":"checkout_started","phone":"+14155550132","properties":{"checkout_url":"https://acme.com/checkouts/c/4b1e"}}'

curl -X POST https://api.jointextkit.com/v1/track \
  -H "Authorization: Bearer $TEXTKIT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"event":"order_placed","phone":"+14155550132"}'
```

A shopper who starts checkout again while a run is waiting stays in that
run: one text per checkout, not one per page view.

## Order and shipping texts

A connected Shopify store also sends two events for its orders:

| Event | When | Properties |
| --- | --- | --- |
| `order_placed` | A new order with a phone number | `order_name` (#1001), `order_url` (the order status page), `total`, `currency`, `items`, `item` |
| `order_shipped` | A shipment gets a tracking number or link | `order_name`, `tracking_number`, `tracking_url`, `tracking_company` |

Two templates in Console → Automations start on them:

```
Acme: thanks for your order #1001. Check its status here:
https://acme.com/... Reply STOP to opt out.

Acme: your order #1001 has shipped. Track it here:
https://www.ups.com/... Reply STOP to opt out.
```

1. Connect the store as above. Order texts need `read_orders`; shipping
   texts from a store connected with one click also use `read_fulfillments`.
2. Create a flow from **Order confirmation** or **Shipping update**, pick
   your sending number, edit the text, and activate it.

A store connected with one click sends each event within seconds. A store
connected with its own token is checked every 10 minutes. Each order and
each shipment counts once. Orders older than a day and shipments older than
a week never start a flow. When a shipment has no tracking link,
`tracking_url` is the order status page.

These texts go only to shoppers whose opt-in is on file with TextKit:
shoppers who ticked the SMS box at your checkout, or who opted in any other
way ([Contacts](/docs/contacts)). Everyone else is skipped, and the run logs
why. Your own verified numbers always get them, so you can test on yourself.
The same holds when you send `order_placed` or `order_shipped` yourself
with `POST /v1/track`.

## Check it

Each run has a timeline in Console → Automations: the event that started
it, the wait, and the text, or why it was held (quiet hours) or skipped
(no opt-in, opted out). Try the flow with a [test key](/docs/sandbox) and your
own number before you turn it on for shoppers.
