---
title: Can you use Meta CAPI without Google Tag Manager?
description: Compare Meta CAPI without GTM using direct code, n8n, Make, or Zapier. Try a synthetic workflow and check consent, stable event IDs, and delivery.
published_at: 2026-09-03
last_updated: 2026-09-17
format: Guide
---

# Can you use Meta CAPI without Google Tag Manager?

Yes. [Meta's Conversions API (CAPI)](https://developers.facebook.com/docs/marketing-api/conversions-api/) accepts server events without Google Tag Manager (GTM). Your application can send events directly or use a managed integration such as Plainrouter. If you also use Meta Pixel, give the browser and server copies the same event name and event ID so Meta can deduplicate them.

This guide compares direct code and automation tools, includes an n8n sandbox starter, and walks through a direct Plainrouter installation. Choose the path that fits the system that owns your conversion event. The installation example uses Plainrouter's managed delivery path; a direct Meta API integration has its own authentication and payload requirements.

## Key takeaways

- Meta CAPI does not require web GTM or a server-side GTM container.
- Removing GTM does not remove the need to manage consent, event mapping, identity data, deduplication, and delivery monitoring.
- If the same conversion is sent through Meta Pixel and CAPI, both copies need the same event name and event ID.
- A direct installation is usually simplest when developers own the website release process and the tracking plan is small.
- Server-side transport is a technical architecture choice. It does not by itself establish legal compliance or permission to process advertising data.

## Can you use n8n, Make, or Zapier for Meta CAPI?

Yes. These tools can send server requests without a GTM container. Choose the sender before building a workflow: direct Meta requests, a native conversion module, and Plainrouter's API use different credentials and payloads.

| Option | Start here when | What you still own |
| --- | --- | --- |
| n8n Facebook Graph API or HTTP Request node | You want to construct and operate direct Meta requests | Meta credentials, payload mapping, retry identity, consent, and delivery diagnosis |
| Make Facebook Conversions API module | Your event already lives in a Make scenario and the native module covers it | The connection, field mapping, consent filters, and avoiding a second sender |
| Zapier Facebook Conversions actions | Your source app and required event are covered by a native Zap | Trigger correctness, supported fields, consent filters, and retry/deduplication behavior |
| n8n or Make HTTP request to Plainrouter | You already use Signals and need its event receipt and delivery trace in a server workflow | The Signals secret, genuine consent and visitor context, stable event identity, and checking destination eligibility |

A native Make or Zapier action can be sufficient; adding another API is not automatically an improvement. n8n already provides a [Facebook Graph API node](https://docs.n8n.io/integrations/builtin/app-nodes/n8n-nodes-base.facebookgraphapi/). See [Make's CAPI module](https://apps.make.com/facebook-conversions-api) and [Zapier's Facebook Conversions setup](https://help.zapier.com/hc/en-us/articles/8496085415949-How-to-get-started-with-Facebook-Conversions-on-Zapier) for their current requirements.

**This is an HTTP integration recipe, not a native Plainrouter connector.** Retrieving Facebook Lead Ads leads is a different job from sending website conversions to Meta. Do not assume an arbitrary CRM row or offline sale satisfies Plainrouter's website-event identity and attribution requirements.

## Try an n8n request without sending a real conversion

[Download the n8n sandbox starter](/n8n-plainrouter-sandbox.json) and import the JSON file into n8n. It has a manual trigger and one HTTP Request node. It is inactive, contains no credentials, and sends only a fictional purchase to Plainrouter's identity-free validation endpoint.

Alternatively, connect a **Manual Trigger** to an **HTTP Request** node with these settings:

| Setting | Value |
| --- | --- |
| Method | `POST` |
| URL | `https://plainrouter.com/api/v1/sandbox/events` |
| Authentication | None |
| Send Body | Enabled |
| Body Content Type | JSON |
| Specify Body | Using JSON |
| Include Response Headers and Status | Enabled |
| Never Error | Disabled |

Use this synthetic body:

```json
{
  "event_id": "sandbox-n8n-order-1042",
  "event_name": "Purchase",
  "action_source": "website",
  "value_data": {
    "value": "49.90",
    "currency": "EUR",
    "order_id": "sandbox-n8n-order-1042"
  }
}
```

Execute the workflow manually. The response should have HTTP `200`, `accepted: true`, `status: "simulated"`, `persisted: false`, and `provider_delivery: false`. With the full-response option enabled, inspect the HTTP node's `statusCode` and `body` fields. The [n8n HTTP Request documentation](https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.httprequest/) explains these settings.

Run it again with the same ID to check that your request stays stable. The sandbox discards each request: it cannot prove production deduplication, consent handling, visitor joins, or Meta delivery. It rejects identity-bearing fields, including `user_data`, `visitor_id`, `click_ids`, and `consent`. Keep real customer records out of this starter. See the [sandbox contract](https://plainrouter.com/docs/api/sandbox).

## Adapt an automation workflow for a real website event

Build a separate production workflow after the sandbox check. **Changing only the endpoint is insufficient.** Start from the [Conversion API contract](https://plainrouter.com/docs/api/conversions) and map a real, verified website purchase:

```text
Verified purchase trigger
    → load the original purchase and current consent decision
    → stop if advertising permission is denied, unknown, or withdrawn
    → map the stable event identity and original event time
    → submit once through the chosen sender
    → inspect the event receipt, then its delivery trace
```

1. Use an authoritative purchase trigger, not a thank-you-page refresh or a manually edited CRM status. Choose one server sender for the purchase. If `signalq('track', ...)` already handles that purchase, do not add an independent automation sender for it.
2. Load the actual consent record and check for withdrawal before each attempted send, including queued retries. An n8n If node or Make filter should continue only when every advertising permission required by your flow is explicitly granted. Missing permission goes to the stopped path. Do not copy a hardcoded grant from an example.
3. Map the original business data using the table below. Do not reuse Meta's `data` envelope: Plainrouter's API expects its own event object.
4. For Plainrouter, use `POST https://plainrouter.com/api/v1/events` with the **Signals workspace secret** in a credential-managed `Authorization: Bearer …` header. An MCP token or Meta token is not interchangeable. Keep secrets out of node expressions, JSON exports, and execution logs.
5. Preserve the HTTP response body and status for diagnosis. New production events return `202`; the same accepted event ID returns `200` with `duplicate: true`. Read acknowledgement warnings before treating the event as linked.
6. Use the event resource or returned `Location` to inspect `deliveries`. A successful node, accepted event, accepted Meta delivery, and attributed Ads Manager conversion are separate results.

| Plainrouter field | Source and retry rule |
| --- | --- |
| `event_id` | A non-personal ID saved for this logical purchase; reuse it on retries |
| `event_name` | The original event name, such as `Purchase`; keep it consistent with a paired Pixel event |
| `event_time` | The original purchase timestamp, not the workflow execution time |
| `event_source` / `action_source` | The actual website source URL and `website`; do not relabel an offline event |
| `consent_basis` / `consent` | The truthful consent basis, advertising flags, decision source, and actual `captured_at` timestamp |
| `visitor_id` or permitted `user_data` | Original, consented visitor context from the documented Signals flow; do not invent a join from an order ID |
| `value_data` | Original decimal-string value, currency, and order ID |

The sandbox request intentionally omits production identity and consent. The production API's [consent warning and delivery guidance](https://plainrouter.com/docs/api/conversions) explains why `202` alone does not establish a valid join. A valid JSON body cannot manufacture a missing observed or attributed website visit.

For Make, the same production rules apply to an HTTP module, while the native Facebook Conversions API module uses Meta's own connection and mapping. Use the platform's credential store and error handling; this n8n starter is not a Make blueprint. Restrict access to workflow execution history because it can retain submitted event data.

## What replaces Google Tag Manager in a Meta CAPI setup?

Without GTM, your application code becomes the point where you initialize tracking, pass consent, and record business events. The server connection can be code you operate yourself or a managed integration.

With Plainrouter, the flow is:

```text
Consent choice
    ↓
Your application → signalq → first-party collection domain → Plainrouter → Meta CAPI
                              ↘ managed Meta Pixel event
```

The browser and server paths describe the same logical conversion. They should not be treated as two independent events.

## What do you need before installing Meta CAPI?

You need:

1. A Plainrouter workspace with an active first-party collection domain.
2. The generated Signals snippet from that workspace.
3. A connected Meta dataset.
4. A consent-management platform or another explicit source of the visitor's current choice.
5. Access to the shared site layout where scripts can be installed.

The browser snippet contains a publishable key. A server secret or Meta access token must never be placed in page HTML or client-side JavaScript.

## How to install Meta CAPI without GTM

### 1. Install the generated Signals snippet

Place the exact snippet shown in Plainrouter **Signals** immediately before the closing `</head>` tag in your shared site layout. It follows this structure:

```html
<script>
    window.signalq =
        window.signalq ||
        function () {
            (window.signalq.q = window.signalq.q || []).push(arguments);
        };
    signalq('init', { key: 'YOUR_PUBLISHABLE_KEY' });
</script>
<script async src="https://YOUR_COLLECTION_DOMAIN/s/v4.js"></script>
```

Keep the initialization block before the asynchronous script. Commands issued while the library loads remain in the queue. Copy the key and script URL from the same workspace-generated snippet instead of assembling them by hand.

### 2. Pass the current consent state

Connect your consent-management platform to `signalq`. Send the current advertising choice after the consent platform resolves it and whenever the visitor changes it.

```js
async function updateAdvertisingConsent(choice) {
    await signalq('consent', {
        consent_basis: 'consent',
        consent: {
            ad_storage: choice.adStorage ? 'granted' : 'denied',
            ad_user_data: choice.adUserData ? 'granted' : 'denied',
            ad_personalization: choice.adPersonalization ? 'granted' : 'denied',
            captured_at: new Date().toISOString(),
            source: 'example_cmp',
        },
    });
}

async function withdrawAdvertisingConsent() {
    await signalq('consent', {
        consent_basis: 'consent',
        withdrawn: true,
    });
}
```

Do not convert an unknown choice into a grant. Plainrouter's browser flow requires all three advertising permissions to be `granted` before an ordinary browser event is sent. The exact legal basis and wording of the consent choice depend on your use case and jurisdiction; this guide only describes the technical integration.

### 3. Identify and track after consent

After your consent policy permits advertising identity use, you can identify the visitor and record the business event from application code:

```js
await signalq('identify', {
    email: 'buyer@example.com',
    phone: '+358401234567',
});

const eventId = await signalq('track', 'Purchase', {
    value: '49.90',
    currency: 'EUR',
    order_id: 'ORDER-1042',
    contents: [{ id: 'SKU-1', quantity: 2 }],
    num_items: 2,
});

console.log('Accepted event ID', eventId);
```

Use a decimal string such as `"49.90"` for money. The returned event ID confirms collector acceptance. It does not by itself prove that Meta accepted the final delivery.

### 4. Connect the Meta destination

In Plainrouter **Signals**, connect Meta, select the correct ad account, and select the dataset that should receive the events. The dataset used by the browser Pixel path must be the same dataset used by the server CAPI path.

After the managed path is validated, remove any other Pixel or CAPI integration that sends the same business event to that dataset. Independent senders can create duplicate conversions when they do not share the same event identity.

## How deduplication works without GTM

GTM does not perform Meta's browser/server deduplication. Meta does. Your integration must give Meta the matching values it needs, and the paired events must reach the same dataset within Meta's deduplication window.

For the same logical conversion:

| Browser Pixel   | Server CAPI     | Requirement                           |
| --------------- | --------------- | ------------------------------------- |
| Event name      | `event_name`    | Use the same logical event name.      |
| `eventID`       | `event_id`      | Use the same stable value.            |
| Dataset / Pixel | Dataset / Pixel | Send both copies to the same dataset. |

When you use Plainrouter's managed browser flow, one `signalq("track", ...)` call creates the event identity used by both paths. Do not add a separate manual Pixel call for that same event. See [how Meta event_id deduplication works](/library/meta-event-id-deduplication) for the matching fields, 48-hour receipt window, and debugging procedure.

## Should you use direct code, web GTM, or server-side GTM?

Choose based on who owns tracking changes and which infrastructure your team can maintain. Web GTM manages browser tags; server-side GTM runs a separate tagging server. Installing a web container alone does not create a CAPI server integration.

| Approach                   | Usually fits when                                                                                                     | Main responsibility                                                                                                 |
| -------------------------- | --------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- |
| Direct application code    | Developers own releases and the tracking plan is small or tightly governed.                                           | Your application deploys consent and event changes.                                                                 |
| Web GTM                    | A team needs controlled browser-tag changes without every change requiring an application release.                    | The container must still preserve consent and stable event data.                                                    |
| Server-side GTM            | A team already operates a server tagging environment or depends on its templates and workflows.                       | The team operates the container, clients, tags, consent mapping, and deduplication.                                 |
| Plainrouter direct install | A team wants a first-party collection path and managed Meta browser/server delivery without operating GTM containers. | The application passes truthful consent and business-event data; Plainrouter manages the eligible destination path. |

GTM can be the better choice when a marketing operations team already has strong container governance and needs to change many browser tags independently. Direct code is often easier to reason about when the event contract belongs in the application and changes should follow the normal code-review process. For a broader comparison of hosting, delivery, and maintenance responsibilities, see [Plainrouter vs server-side GTM vs direct platform APIs](/compare).

## Common mistakes

### Loading the script before its queue exists

Initialize `window.signalq` before loading the asynchronous library. Reversing the order can lose calls made during page startup.

### Using a server secret in the browser

Only the workspace's publishable key belongs in the snippet. Keep server credentials and Meta access tokens outside browser code.

### Treating consent as a one-time page-load value

Pass the resolved state and update it when the visitor changes or withdraws the choice. Do not assume missing values are granted.

### Sending the same purchase from multiple integrations

A legacy Pixel, a GTM container, a commerce-platform integration, and a new CAPI sender can all report the same purchase. Choose one owner for the paired browser/server path.

### Generating unrelated browser and server IDs

Matching event names without matching event IDs is not a complete deduplication setup. Assign one stable identity to the logical conversion and preserve it across retries and delivery paths.

### Treating ingestion as final delivery

A successful request to the first-party collector proves that the collector accepted the event. Check the destination delivery state separately.

## How to debug the installation

Start with one consented test conversion and follow it from the browser to the configured Meta dataset. A loaded script or successful collector request is only one step in that path.

1. Open browser developer tools and confirm `/s/v4.js` loads from the configured collection domain.
2. Confirm initialization alone does not send an ordinary event while consent is unresolved.
3. Grant consent through the real consent interface and confirm the browser sends the expected event request.
4. Inspect the event name, value, currency, and order reference.
5. Confirm the browser Pixel and server CAPI copies use the same event ID.
6. Check that you are inspecting the same Meta dataset selected in Plainrouter.
7. Review the event's destination delivery state instead of relying only on the browser request.
8. If counts are inflated, search the site, GTM containers, plugins, and commerce integrations for another Pixel or CAPI sender.

If your site uses Content Security Policy, allow the generated collection origin in `script-src` and `connect-src`. A managed Meta Pixel also uses Meta browser endpoints after consent is granted. Use your browser's CSP reports to identify the exact origins required by the current integration, and apply your existing nonce or hash policy to inline initialization code instead of broadening the policy with wildcards.

Before switching off the old integration, verify three outcomes independently: the collector accepted the event, the Meta destination accepted delivery, and Meta recognized any browser/server pair. If you need a backend integration instead of a browser install, use the [Node.js and TypeScript](https://plainrouter.com/docs/guides/meta-capi/nodejs), [Python](https://plainrouter.com/docs/sdk/python#send-a-meta-conversion), or [Go](https://plainrouter.com/docs/sdk/go#send-a-meta-conversion) guide.

## Frequently asked questions

### Is Google Tag Manager required for Meta CAPI?

No. Meta CAPI accepts server events independently of GTM. GTM is one way to deploy and transform those events, not a protocol requirement.

### Can I remove GTM but keep Meta Pixel and CAPI?

Yes. Install the browser integration directly in your application and keep the server connection separately. The same conversion still needs coordinated event naming and identity across both paths.

### Can I send only server events?

Yes. A server-only implementation can send CAPI events without a browser Pixel. You will not have a browser copy to deduplicate, and you should send only the permitted event and identity context you actually possess.

### Does server-side tracking bypass consent requirements?

No. Moving transport to a server does not create permission to collect or send advertising data. Capture and propagate the real consent state required by your implementation and obtain legal advice for your specific obligations.

### Should I keep GTM?

Keep it when its container workflow, templates, and change controls are valuable to your team. Remove it when it adds an unnecessary deployment layer and your developers can own the smaller event contract directly.

## Sources

- [Meta Conversions API documentation](https://developers.facebook.com/docs/marketing-api/conversions-api/)
- [Meta Business SDK Conversions API implementation](https://github.com/facebook/facebook-php-business-sdk#conversions-api)
- [Install the Plainrouter Signals pixel](https://plainrouter.com/docs/signals/install-pixel)
- [Track events and consent with Plainrouter](https://plainrouter.com/docs/signals/track-events)
- [Connect a Meta dataset](https://plainrouter.com/docs/signals/connect-meta)

## Related guides

- [Troubleshoot missing Meta Pixel and CAPI events](/library/meta-capi-missing-duplicate-events)

- [How does Meta event_id deduplication work?](/library/meta-event-id-deduplication)
- [How does consent work for server-side tracking?](/library/server-side-consent)
- [What is fbclid? How it relates to fbp and fbc](/library/fbp-fbc)

## Check your payload locally

Use the free [Meta CAPI payload check](/library/meta-capi-missing-duplicate-events#local-check) before a real delivery test.
