# Ekx Meta Ads

> Meta Graph and Marketing API integration — campaign/ad set/ad structure, creatives, insights, catalogs and pixel events, plus WhatsApp Business messaging. Use when creating or reading ad campaigns, pulling performance insights, managing a product catalog, boosting a post, or wiring WhatsApp Business notifications.

- Skill: `ekinoxis-evm/ekx-meta-ads` (Agent Skill)
- Install (CLI): `npx skillmds@latest add ekinoxis-evm/ekx-meta-ads`
- Raw SKILL.md: https://api.skillmd.com/api/skills/ekinoxis-evm/ekx-meta-ads/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Marketing & Growth
- Author: Ekinoxis-evm (https://skillmd.com/u/ekinoxis-evm)
- Updated: 2026-09-22
- Page: https://skillmd.com/skills/ekinoxis-evm/ekx-meta-ads

---


# Meta (Ads + WhatsApp)

Our growth stack. The **`meta-ads` MCP server** is by far the largest server in the
portfolio by tool count — prefer it over hand-rolled Graph calls for anything Claude
does at build time.

---

## Environment

```bash
META_ACCESS_TOKEN=              # SECRET — read
META_ADS_WRITE_TOKEN=           # SECRET — write, kept separate deliberately
META_APP_SECRET=                # SECRET
META_AD_ACCOUNT_ID=             # act_XXXXXXXXX  (the act_ prefix is required)
META_PAGE_ID=
META_PIXEL_ID=
META_API_VERSION=               # v21.0 — PIN IT
META_GRAPH_VERSION=
META_WHATSAPP_TOKEN=            # SECRET
META_WHATSAPP_PHONE_NUMBER_ID=
META_WHATSAPP_VERIFY_TOKEN=     # SECRET — webhook handshake
```

**Separate read and write tokens.** Worth doing everywhere: the read token powers dashboards and can be scoped narrowly; the write token can create
campaigns and spend money, and should be used from exactly one code path.

**Pin `META_API_VERSION`.** Meta deprecates versions on a ~2-year clock and changes
field availability between them without warning at the call site.

---

## Hierarchy

```
Ad Account (act_…)
└── Campaign        — objective, budget strategy
    └── Ad Set      — audience, placement, schedule, budget
        └── Ad      — creative + tracking
```

Create top-down; you cannot make an ad without an ad set. Every object starts in
`PAUSED` — **always create paused, review, then activate**. A misconfigured audience
that goes live immediately spends real money before anyone looks at it.

---

## Insights

```
GET /v21.0/act_<ID>/insights
  ?fields=spend,impressions,clicks,ctr,cpc,actions
  &level=ad
  &time_range={"since":"2026-08-01","until":"2026-08-24"}
  &time_increment=1
```

Notes that matter:
- **Attribution windows** change the numbers. `action_attribution_windows` defaults differ from the Ads Manager UI, so an API number that "disagrees with the dashboard" is usually this.
- **Insights lag.** Today's numbers are incomplete for up to 72 hours. Never chart today as final.
- **`actions` is an array of typed objects**, not a scalar. Purchases are the entry with `action_type: "purchase"` (or `omni_purchase` — pick one and be consistent).

---

## Catalogs

A product catalog feeds dynamic ads. The catalog is a separate object from the
ad account, with its own feeds, product sets and rules. Keep Supabase as the source of
truth and push a feed, rather than editing products in Meta directly — same principle
as Shopify inventory ([`../ekx-shopify/SKILL.md`](../ekx-shopify/SKILL.md)).

---

## WhatsApp Business

```ts
await fetch(`https://graph.facebook.com/${V}/${process.env.META_WHATSAPP_PHONE_NUMBER_ID}/messages`, {
  method: "POST",
  headers: { Authorization: `Bearer ${process.env.META_WHATSAPP_TOKEN}`, "Content-Type": "application/json" },
  body: JSON.stringify({
    messaging_product: "whatsapp",
    to: phone,                                   // E.164, no +, no spaces
    type: "template",
    template: { name: "order_ready", language: { code: "es" } },
  }),
});
```

Three rules:
1. **Outside the 24-hour customer service window you can only send approved templates.** Free-form text is rejected. Get templates approved before building the flow around them.
2. **Numbers are E.164 without `+`.** Colombian numbers: `57` + 10 digits.
3. **Webhook verification** is a GET with `hub.verify_token` — echo `hub.challenge` back as plain text if it matches `META_WHATSAPP_VERIFY_TOKEN`, or Meta never subscribes you.

`shopper` sells via WhatsApp with cash-on-delivery, so this is the primary commerce
channel there, not a notification sideline.

---

## Gotchas

1. **Missing `act_` prefix** on the account id → confusing 400.
2. **Creating unpaused** spends money before review.
3. **Insights lag ≤72h.**
4. **Attribution window mismatch** vs the UI.
5. **Token expiry** — long-lived tokens still expire (~60 days). Have a refresh path, or the dashboards die quietly.
6. **Unpinned API version.**
7. **WhatsApp 24-hour window.**

