Recipe: Shopify (Flow)
Fire the template send from Shopify Flow, with no code and no server of your own.
This is the guided recipe for the generic
Template Messages contract: the same POST, walked
through step by step from Shopify.
Shopify does not call this API on its own, but Shopify Flow — the official automation
app, free — can: it fires on the abandoned cart and makes the POST. It is the route with
no code and no server of your own in the middle.
Before you start
| Requirement | Where you sort it out |
|---|---|
| Approved template | With its exact technical name and language — GET /v1/templates lists them. |
| API key | With the outbound:send scope to send, plus outbound:read if you are going to check the status — see Authentication. |
| Phone required at checkout | Shopify → Settings → Checkout: Customer contact method set to "Phone number or email", and Shipping address phone number set to "Required". With no phone on the cart there is nobody to write to. |
| Native recovery email turned off | Shopify → Marketing → Automations: if WhatsApp takes over, disable the native "Abandoned checkout". Otherwise the customer gets two reminders for the same cart. |
The workflow
- Trigger — Customer abandons checkout.
- Condition —
abandonment.customer.defaultPhoneNumberis not empty. A checkout with no phone cannot be sent: cutting it here saves spending the call on a422. - Action — Send HTTP request, with method
POST, the URLhttps://api.ya.onl/v1/template-messagesand two headers:X-API-Keywith your key andContent-Type: application/json.
The body
{
"event_id": "CART-{{ abandonment.id | split: '/' | last }}",
"phone": "{{ abandonment.customer.defaultPhoneNumber.phoneNumber }}",
"template": { "name": "YOUR_TEMPLATE", "language": "es" },
"variables": {
"1": "{{ abandonment.customer.firstName }}",
"2": "{% for item in abandonment.abandonedCheckoutPayload.lineItems limit: 3 %}{{ item.quantity }}× {{ item.title | replace: '\"', '' }}{% unless forloop.last %}, {% endunless %}{% endfor %}"
},
"occurred_at": "{{ abandonment.createdAt }}"
}| Field | What that line does |
|---|---|
event_id |
Trims Shopify's GID (gid://shopify/Abandonment/774830424) down to its last segment, which matches the field's charset, and the CART- prefix covers the 8-character minimum and gives you idempotency: if Flow retries, the customer does not get two messages. |
phone |
defaultPhoneNumber.phoneNumber already comes in E.164 (+51987654321), which is exactly what the field requires. |
template |
An object with your template's name and language, exactly as approved in Meta: replace YOUR_TEMPLATE and adjust language. |
variables |
By position, in your template's order: "1" the name, "2" the first three products. Match the count to what the template requires — a missing one is a 422. The replace strips quotes out of the title, which would break the JSON. |
occurred_at |
abandonment.createdAt already carries an offset, and it is what switches on the freshness window: an old abandonment comes back discarded with reason stale instead of being sent late. |
The recipe is generic. The contract is the same from any platform able to make a
POST — another ecommerce, your own backend, or an automation tool like Zapier or Make.
Shopify Flow is only the guided example, because it solves the abandonment trigger
without writing code.
Check that it went out
curl https://api.ya.onl/v1/template-messages/CART-774830424 \
-H "X-API-Key: yv_live_v1_your_api_key"With the same key — as long as it has outbound:read — and the same event_id Flow built.
A 404 means the call never arrived: check the workflow's run history in Flow.
