VoidhashDocs
DocumentationAPI reference
Dashboard
Get started
Introduction
Quickstart
Setup
Setup overview
Create a project
Products and perks
Paywall locations
CLI
Paywall components
Components
Develop a component
Editor panels
Component definition
Publishing
Installing and capabilities
SDKs
Swift
Kotlin
React Native
Web
Store setup
Connect the App Store
Connect Google Play
Connect Stripe
API libraries
Node.js
PHP
Rust
Go
Resources
MCP server
Meta ads
Migrate in observer mode
Delete a person's data

Meta ads

Connect a Meta ad account to compare campaign spend with the trials, payers and revenue it brings in.

Connect a Meta (Facebook and Instagram) ad account to your project and Voidhash keeps a read-only copy of its campaigns and daily spend. It joins that spend with the people each campaign, ad set and ad brought in, and with what those people went on to do, so one report shows cost per trial, cost per payer and the return on ad spend measured on real revenue.

Voidhash only reads from Meta. It never changes a campaign, budget or ad.

Before you start

Voidhash attributes people to an ad from the ad's landing page URL. Add the campaign, ad set and ad IDs to every ad's URL parameters and install the web SDK on the landing page, as described in Page views and attribution. Without the IDs, spend still syncs, but no people are matched to it.

You also need:

  • Admin access to the business in Meta Business Manager that owns the ad account.
  • An app owned by that business. Any app type works; it is only used to issue the token.
  • A Voidhash user or secret key with the project:ads:manage permission to connect the account.

Create a system user token

A system user is a non-human user of your business. Its token keeps working when people leave your team and does not need Meta's app review.

Add a system user

In Business Settings, open Users → System users and select Add. Name it, for example Voidhash reporting, and give it the Employee role.

Give it the ad account

With the system user selected, choose Assign assets, then Ad accounts. Select the ad account and turn on View performance. Voidhash needs no other access.

Generate the token

Select Generate new token, choose your business's app, and pick the ads_read permission. For the expiration, choose Never so the sync keeps running. Copy the token; Meta shows it once.

Find the ad account ID

In Ads Manager, open the account menu. The ad account ID is the number shown under the account name. You can enter it with or without the act_ prefix.

Treat the token like a password. Voidhash stores it encrypted and never shows it again, in the dashboard or through the API.

Connect the account

In the dashboard, open Settings → Ad accounts and select Connect Meta ad account. Enter the ad account ID and the token. Voidhash checks that the token grants ads_read and can read the account, then records the account's name, currency and time zone. If the check fails, the message says what to fix.

Through the API, send the same values to POST /api/v1/ad-accounts:

curl https://api.voidhash.com/api/v1/ad-accounts \
  -H "x-secret-key: $VOIDHASH_SECRET_KEY" \
  -H "content-type: application/json" \
  -d '{
    "network": "meta",
    "accountId": "act_1234567890",
    "accessToken": "EAAB...",
    "backfillDays": 90
  }'

Connecting an account that is already connected replaces its token. Use this when you rotate the token or after a sync stopped. A project can connect several ad accounts.

What is synced, and how often

The first sync starts as soon as the account is connected. After it, Voidhash syncs every account about every six hours.

  • Campaigns, ad sets and ads: IDs, names, status and effective status, campaign objective, daily and lifetime budgets, and when each was created and last updated. Objects Meta stops listing, such as deleted ones, keep their last known name.
  • Daily delivery per ad: spend, impressions, clicks, and Meta's reported purchases, subscriptions, trial starts and app installs with their values, for ads in every status, including paused, archived and deleted ones. Each day is dated in the ad account's time zone, and amounts are in its currency. Conversions are counted with the ad account's attribution setting, the same numbers Ads Manager shows, and the setting is stored with each day. Meta reports one conversion under several overlapping action types, so each kind counts the first type present, starting with the cross-channel total (such as omni_purchase), instead of adding them up.
  • Daily account totals: the whole account's spend, impressions and clicks per day. They include spend Meta no longer reports ad by ad, such as on some deleted ads, so totals stay complete.

The first sync pulls the last 90 days, or the number of days you set with backfillDays, up to three years. Meta keeps adjusting delivery and attributed conversions for up to 28 days, so every later sync pulls the last 28 days again and replaces what it stored for the ads Meta reports. Days stored for an ad Meta no longer reports are kept. Connecting the account again with a larger backfillDays pulls the longer history on the next sync.

Each account in Settings → Ad accounts shows when it last synced, the last error, and when the next sync runs.

  • A sync that fails because Meta is unavailable or rate-limits the request is retried an hour later, or later still when Meta asks for a longer pause. The status shows Sync failing until a sync succeeds.
  • When the token is revoked or expires, or loses access to the ad account, syncing stops and the error says why. Generate a new token and connect the account again to resume.

Disconnecting an account deletes everything synced from it. Connecting, reconnecting and disconnecting are recorded in the project's audit log, without the token.

Campaign performance

The Analytics → Campaigns page, the campaign performance endpoint and the campaign_performance MCP tool report one row per campaign, ad set or ad for a range of days:

curl https://api.voidhash.com/api/v1/analytics/queries/campaign-performance \
  -H "x-secret-key: $VOIDHASH_SECRET_KEY" \
  -H "content-type: application/json" \
  -d '{ "from": "2026-09-01", "to": "2026-09-30", "level": "campaign" }'

Rows include campaigns with spend but nobody attributed, and campaign IDs people arrived from that no connected account has synced (isSynced is false). Account spend that no ad row accounts for, usually on deleted ads, is reported as unallocated and counted in totals, so the total spend matches the account. Reading the report requires project:ads:read and project:analytics:read.

MetricMeaning
spend, impressions, clicksMeta's delivery for the days in the range.
metaConversionsPurchases, subscriptions, trial starts and app installs Meta reports, with their values.
peopleAcquiredPeople whose first visit came from the ad. People Voidhash already knew are counted as returningPeople only.
trialStarters, trialConversionsAcquired people who started a trial, and those whose trial turned into a paid period.
payingPeopleAcquired people who paid anything.
grossRevenue, refunds, netRevenueWhat acquired people paid, what was refunded, and the difference.
costPerAcquisitionspend / peopleAcquired.
costPerTrialspend / trialStarters.
costPerPayerspend / payingPeople.
roasTrue return on ad spend: netRevenue / spend.
metaRoasMeta's purchase return on ad spend: Meta's reported purchase value divided by spend.

A ratio is null when what it divides by is zero. People are attributed by their first touch, as in the acquisition report, so roas counts each person's revenue once, on the ad that first brought them in. metaRoas uses Meta's own attribution and can count the same purchase for several ads; the two often differ.

By default money is reported in the ad account's currency and days follow its time zone, so spend and outcomes line up with Ads Manager. You can ask for another currency; spend is then converted at each day's exchange rate. When you ask for another timezone, people and revenue follow it but spend stays dated in the account's time zone, and the report adds a warning. Accounts with different currencies or time zones need an explicit currency and timezone, or one adAccountId.

Revenue comes from production purchases unless you set environment. Recent days are incomplete: spend for today is still accruing, and people acquired recently have had less time to convert. Use revenueWindow to compare cohorts over the same number of days.

PreviousMCP serverNextMigrate in observer mode

On this page

Before you startCreate a system user tokenAdd a system userGive it the ad accountGenerate the tokenFind the ad account IDConnect the accountWhat is synced, and how oftenCampaign performance