API and developers

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

  1. Trigger — Customer abandons checkout.
  2. Condition — abandonment.customer.defaultPhoneNumber is not empty. A checkout with no phone cannot be sent: cutting it here saves spending the call on a 422.
  3. Action — Send HTTP request, with method POST, the URL https://api.ya.onl/v1/template-messages and two headers: X-API-Key with your key and Content-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.

Need help?