The safest Facebook Ads tracking setup does not put every value into a long UTM string and hope analytics reconstructs it later. It passes the three stable Meta IDs into named tracker fields, uses sub IDs for placement and source, and verifies the final URL on a real click.
For DarkCore, paste this into the URL parameters field at ad level:
campaign_id={{campaign.id}}&adset_id={{adset.id}}&ad_id={{ad.id}}&sub1={{placement}}&sub2={{site_source_name}}
If you are building the complete destination URL manually, the first parameter needs ?:
https://go.example.com/?campaign_id={{campaign.id}}&adset_id={{adset.id}}&ad_id={{ad.id}}&sub1={{placement}}&sub2={{site_source_name}}
That small distinction is responsible for a surprising number of broken launches: a parameters-only field normally expects no leading ?, while a complete URL needs exactly one ? before the first key and & before every additional key.
What “Facebook sub ID” actually means
Meta does not provide a special macro called sub_id. A sub ID is a field owned by your tracker or affiliate network. Its value can be filled by one of Meta’s dynamic URL parameters.
For example:
sub1={{placement}}
Here sub1 is DarkCore’s field, while {{placement}} is the Meta macro. When the ad is clicked, Meta replaces the macro with a real value and the tracker receives something like:
sub1=facebook_feed
The same principle works for campaign, ad set and ad IDs. DarkCore has dedicated fields for those IDs, so there is no reason to consume three generic sub slots for data that already has a named home.
Recommended Meta-to-DarkCore mapping
| Incoming parameter | Meta value | What it identifies | Why this field |
|---|---|---|---|
campaign_id | {{campaign.id}} | Campaign | Stable join key for reporting and cost data |
adset_id | {{adset.id}} | Ad set | Stable audience/bid-group key |
ad_id | {{ad.id}} | Ad | Most granular stable ad key |
sub1 | {{placement}} | Feed, Stories, Reels and other placements | Useful routing and profitability breakdown |
sub2 | {{site_source_name}} | Facebook, Instagram, Messenger or Audience Network source | Separates inventory source from placement |
Meta’s dynamic URL parameter reference also exposes name-based values such as {{campaign.name}}, {{adset.name}} and {{ad.name}}. Use them only as readable labels, not as primary keys. Names can be edited, duplicated or reused; IDs are the safer fields for joining click, spend and conversion data.

If your team needs human-readable labels in the click log, add them after the stable fields:
campaign_id={{campaign.id}}&adset_id={{adset.id}}&ad_id={{ad.id}}&sub1={{placement}}&sub2={{site_source_name}}&sub3={{campaign.name}}&sub4={{adset.name}}&sub5={{ad.name}}
Do not replace the ID fields with those names.
Where fbclid fits—and where it does not
fbclid and sub IDs solve different problems.
fbclidis Meta’s click identifier. When present on the incoming URL, DarkCore captures it for click continuity and Meta attribution signals.campaign_id,adset_idandad_iddescribe the account structure that produced the click.sub1–sub10are flexible business dimensions you control: placement, source, angle, buyer code, creative batch or another segmentation value.
Do not put fbclid into sub1 and do not manufacture it from an ad ID. Let Meta append the real click identifier, while your URL parameter template carries the campaign context you want to report on.
If Pixel and Conversions API can report the same business event, keep this click context separate from the shared event identity and use the Facebook CAPI deduplication checklist before launch.
How DarkCore carries the values forward
At the first request, DarkCore reads the named campaign fields and sub1 through sub10 from the query string. You can then reference those captured values in a Track Link:
https://affiliate.example/click?cid={click_id}&campaign={campaign_id}&adset={adset_id}&ad={ad_id}&placement={sub1}&source={sub2}
The brace syntax here belongs to DarkCore, not Meta. The two stages are deliberately different:
- Meta replaces
{{ad.id}},{{placement}}and the other double-brace macros before the landing request. - DarkCore stores the received values, then replaces
{ad_id},{sub1}and{click_id}when it builds the outbound URL.
If a literal {{ad.id}} reaches DarkCore, the Meta stage did not expand it. If a literal {sub1} reaches the affiliate URL, the outbound Track Link mapping needs investigation.
For the later conversion half of the chain, see how to track Facebook affiliate conversions and the S2S postback guide.
When the network asks for {subid}, {aff_sub} or another unfamiliar token, use the click ID, sub ID and postback macro mapping guide before copying the template.
The pass/fail launch test
Do not approve this setup because the template “looks right.” Record one real or platform-generated test click and fill this table.
| Test | Expected fact | Pass? |
|---|---|---|
| Final landing URL | Contains one ?, then valid key=value pairs separated by & | ☐ |
| Macro expansion | No literal {{campaign.id}}, {{ad.id}} or other Meta token remains | ☐ |
| Stable IDs | campaign_id, adset_id and ad_id contain numeric IDs | ☐ |
| Placement | sub1 contains the actual placement, not a hard-coded guess | ☐ |
| Source | sub2 separates Facebook/Instagram/other Meta inventory | ☐ |
| Click record | The same values appear on the DarkCore click | ☐ |
| Outbound link | {click_id} and required captured fields are expanded once | ☐ |
| Test conversion | One postback attaches to the same click without duplicate revenue | ☐ |

Preview tools are useful for syntax, but the decisive evidence is the click record and final browser URL. Some preview paths do not behave exactly like a delivered ad click.
Common failures
The first parameter has no question mark
This is correct inside Meta’s dedicated URL parameters field, but wrong when you paste the same string directly after a domain. A full URL such as https://go.example.com/sub1={{placement}} treats sub1 as part of the path. It must be https://go.example.com/?sub1={{placement}}.
Every value is stored in UTMs only
UTMs are useful for web analytics, but a tracker still needs deterministic fields for routing, click lookup and postback reconciliation. Keep the stable IDs in named fields even if you also add a UTM layer.
Names are used as database keys
Campaign names are readable but operationally weak. A rename can split reports; duplicated names can merge unrelated traffic. Store IDs first, names second.
Meta macros and DarkCore macros are mixed
{{ad.id}} belongs in the ad URL parameters. {ad_id} belongs in a DarkCore outbound Track Link. Swapping the syntax leaves a literal token in the URL.
The team tests only the redirect
A 200 page load or a successful redirect proves transport, not attribution. The acceptance result is a complete row: incoming IDs, stored click, expanded outbound URL and exactly one conversion on the original click.
FAQ
Which Facebook macro should go into sub1?
For the recommended DarkCore mapping, use sub1={{placement}}. Campaign, ad set and ad IDs have dedicated fields, so placement is a more useful first custom dimension.
Can I use campaign name instead of campaign ID?
You can store the name as an additional label, but use {{campaign.id}} as the primary reporting and join key.
Is fbclid the same as a Facebook sub ID?
No. fbclid identifies a specific Meta click. A sub ID is a custom tracker field used to store a dimension such as placement, source or creative batch.
How many sub IDs does DarkCore support?
DarkCore captures sub1 through sub10 on the incoming click and exposes the same values as {sub1} through {sub10} in outbound Track Links.