Macros

Every token a pixel URL, a request body, a bid template or a SIP header can use — what each one expands to, and the rules that decide how.

After this page you can write any template the platform accepts and know exactly what will be on the wire. A macro is a placeholder of the form {{namespace.field}}. Wherever you supply a template — a pixel URL or body, a custom header, the bid request of an external RTB target, a delivery endpoint's SIP headers — macros are replaced with values from the call in hand.

There is one vocabulary, listed below, and the platform uses it everywhere. What changes between places is not which macros exist but how their values are encoded, and whether caller identity is allowed through at all.

The six rules

  1. A macro is a namespace and a field. {{call.ref}}, {{caller.state}}, {{tag.gclid}}. There is no arithmetic, no conditionals, no functions and nothing that could be made to run — the whole language is "look up this value".
  2. A missing value expands to an empty string, never to its own braces. A macro that has no value for this call, and a macro that does not exist at all, both come out as nothing. A partner's parser copes with &value=; it does not cope with &value={{call.revnue}}.
  3. Values are encoded for where they land. Percent-encoded in a URL, JSON-string-escaped in a JSON body, verbatim in a SIP header or a whisper. You never escape anything yourself.
  4. Caller identity never reaches a URL. In a URL the caller-number macros become {{caller.hash}} instead. See Identity in a URL.
  5. Spaces inside the braces are fine. {{ call.id }} and {{call.id}} are the same macro.
  6. Three opening braces send a literal {{ through, for a partner whose own template language uses braces: {{{call.id}} arrives as {{call.id}}.

Event

Which delivery this is. Useful in a body a partner de-duplicates on, and in a URL when one endpoint serves several events.

MacroValueExample
{{event.name}}The event that fired this delivery.call.converted
{{event.id}}The delivery's request id — the same across retries, so a receiver can de-duplicate on it.req_5f0c2a91b7d34e10

Call

MacroValueExample
{{call.id}}The call's id.0b9f6c1e-52a4-4a0e-9f0a-3d1c7e8b2a11
{{call.ref}}The call's public reference, as the console prints it.CA-482137
{{call.status}}The call's status as the event fired — completed once it has ended with a buyer.completed
{{call.duration}}Seconds the caller spent talking to the buyer. A whole number, so it can go into a JSON body unquoted.184
{{call.converted}}1 when the call earned its price, otherwise 0.1
{{call.revenue}}What the buyer owes for the call, in dollars with two decimal places.42.00
{{call.payout}}What the publisher is owed for the call, in dollars.27.50
{{call.started_at}}When the call arrived (ISO 8601, UTC).2026-09-20T14:03:11.000Z
{{call.answered_at}}When the buyer answered. Empty if nobody did.2026-09-20T14:03:19.000Z
{{call.ended_at}}When the call ended.2026-09-20T14:06:23.000Z
Note the underscores in the three timestamps. {{call.startedAt}} is not a macro and is refused when a pixel is saved.

Caller

MacroValueExampleIn a URL
{{caller.hash}}A stable 16-character hash of the caller's number — safe in a URL, and the same for the same caller every time.9c1185a5c5e9fc54As is
{{caller.e164}}The caller's number. In a URL this becomes the hash; it expands in a POST body.+14155550142Hashed
{{caller.digits}}The caller's number, digits only.14155550142Hashed
{{caller.nanp}}The caller's ten-digit number.4155550142Hashed
{{caller.areaCode}}The caller's area code.415As is
{{caller.prefix}}The caller's exchange — the three digits after the area code.555As is
{{caller.suffix}}The last four digits of the caller's number.0142Hashed
{{caller.state}}The caller's state, from their area code.CAAs is
{{caller.city}}The caller's city, when a publisher sent one with their ping.San FranciscoAs is
{{caller.zip}}The zip code a publisher sent with their ping.94103As is
caller.city and caller.zip are things a publisher sent. They are empty for a call that was dialled rather than won at auction, because nobody ever told us.

caller.suffix is on the hashed list and the area code is not, which looks inconsistent until you count: the number is only three macros long, and {{caller.areaCode}}{{caller.prefix}}{{caller.suffix}} would rebuild it in full in a query string. The area code and the exchange say roughly where a caller is and stay usable; the last four digits are what make it that caller, so they are the part withheld.

Number, campaign, publisher, buyer and target

MacroValueExample
{{number.e164}}The tracking number that was dialled.+18885550100
{{number.digits}}The dialled number, digits only.18885550100
{{campaign.id}}The campaign's id.6a2d1f0e-8b7c-4c55-a1d3-0e9f8a7b6c5d
{{campaign.name}}The campaign's name.Acme Health — Inbound
{{publisher.id}}The publisher the call came from. Empty for your own media.c4e1a2b3-9d8f-4f6e-b5a4-1c2d3e4f5a6b
{{publisher.name}}The publisher's name.Northwind Media
{{publisher.ref}}The publisher's own id for the call, as sent with their ping. Empty for a call that was dialled rather than won at auction.nw-88231
{{buyer.id}}The buyer the call was sold to.f1e2d3c4-b5a6-4978-8a9b-0c1d2e3f4a5b
{{buyer.name}}The buyer's name.BlueSky Legal
{{target.id}}The target that took the call.a9b8c7d6-e5f4-4a3b-9c2d-1e0f9a8b7c6d
{{target.name}}The target's name.BlueSky Legal — Intake
{{target.externalRef}}Your own reference for the target that took the call, as it stood when the call was routed.bsl-intake
The tracking number is the workspace's own, printed on its own landing page — it is not caller identity, and it is never hashed.

Tags

MacroValueExample
{{tag.<key>}}Any tag on the call — a URL parameter your number-insertion snippet captured, or a field a publisher sent.{{tag.gclid}}Cj0KCQjw
  • The key may contain dots. Everything after tag. is the key, so {{tag.utm.source}} reads the tag literally named utm.source.
  • Only scalar values expand. A tag whose value is an object or an array comes out empty rather than as [object Object].
  • A tag that is not on this call is empty, like any other missing value. There is no way to tell "no such tag" from "tag with an empty value", and a partner should not need to.
  • In a URL, tags expand on your own pixels and are empty in a template a buyer wrote. See below.

Time

MacroValueExample
{{now.iso}}When this was sent (ISO 8601, UTC).2026-09-20T14:06:24.512Z
{{now.epoch}}When this was sent, in seconds since 1970.1789913184
{{now.epochMs}}When this was sent, in milliseconds since 1970.1789913184512
"Now" is the moment the request is built, so a retry an hour later carries a later now than the first attempt. Use {{call.started_at}} when you mean the call.

Bidding

These describe an auction. They belong in an external RTB target's bid template and in a delivery endpoint's templates, where there is an auction to describe. They are not available to a pixel — a tracking event has no auction behind it, so they would all be empty, and a pixel that uses one is refused when you save it.

MacroValueExample
{{auction.id}}The auction's id (bid requests only).3f2e1d0c-9b8a-4765-8432-10fedcba9876
{{auction.ref}}The auction's public reference (bid requests only).AU-771204
{{bid.id}}The winning bid's id (delivery templates only).7c6b5a49-3827-4615-9048-372615049382
{{bid.amount}}The winning bid, in dollars.38.00
{{bid.amountCents}}The winning bid, in cents.3800
{{terms.durationSeconds}}Seconds of talk time the bid converts at.90
{{postback.url}}Where the buyer reports a conversion for this call.https://api.example.com/api/rtb/v1/conversion
{{postback.key}}The per-call key that authenticates that report.pbk_live_Zk3…

The three modes

The same template expands differently depending on what it is. You do not choose the mode; where the template lives chooses it.

ModeUsed forWhat it does to a value
URLA pixel's URL. An outbound bid URL.Percent-encodes it, and applies the identity rule below. A space becomes %20, an & inside a value becomes %26, so a tag can never add a parameter of its own.
JSONA pixel's body when its content type is JSON. A bid request body.Escapes it as a JSON string — quotes, backslashes, control characters. Identity expands in full.
RawCustom SIP headers, whisper text.Substitutes it verbatim. Header values additionally have line breaks replaced by spaces and are cut at 2,000 characters, so a tag can never become a second header.

Identity in a URL

A URL ends up in access logs, referrer headers and analytics pipelines on both sides. A phone number there is a disclosure nobody chose, so it is prevented in code rather than discouraged in a note.

In a URL templateWhat actually goes on the wire
{{caller.e164}}, {{caller.digits}}, {{caller.nanp}}, {{caller.suffix}}{{caller.hash}} — the stable 16-character hash.
{{tag.anything}} in your own pixelThe tag's value. Handing {{tag.gclid}} back to the network that issued it is what a pixel is for.
{{tag.anything}} in a template a buyer wroteEmpty. A publisher who puts an email address in a tag should not have it appear in a buyer's access log.
A tag whose value contains the caller's ten digitsThe hash, even on your own pixel. The rule is about the number, not about which macro carried it.
Every other macroIts value, percent-encoded.
  • There is no opt-in for a pixel. A pixel URL never carries a caller's number, whatever you type.
  • A POST body is not a URL — it is not logged by intermediaries — so identity expands there in full. If a partner needs the real number, send it in a body.
  • The hash is the same for the same caller every time, so a partner can de-duplicate or suppress on it without ever holding the number.
  • The console tells you when a template you are saving is affected, rather than letting you find out from a partner's empty field.

Worked example: the same template, two ways

A conversion postback to an ad network, fired on call.converted for the call in the payload sample. As a GET pixel, its URL is expanded in URL mode:

The template you save
https://ads.example.com/conversion?click={{tag.gclid}}&value={{call.revenue}}&ref={{call.ref}}&caller={{caller.e164}}
The request that is actually made
https://ads.example.com/conversion?click=Cj0KCQjw&value=42.00&ref=CA-482137&caller=9c1185a5c5e9fc54

{{tag.gclid}} expanded because it is your own pixel and the tag is yours. {{call.revenue}} arrived as dollars. {{caller.e164}} became the hash without being asked — the network gets something it can de-duplicate on and nothing it can dial.

The same facts as a POST body to a partner's API, expanded in JSON mode, where identity is allowed:

The body template
{
  "external_id": "{{call.ref}}",
  "phone": "{{caller.e164}}",
  "state": "{{caller.state}}",
  "seconds": {{call.duration}},
  "payout": "{{call.payout}}",
  "source": "{{publisher.name}}",
  "click_id": "{{tag.gclid}}"
}
What is sent
{
  "external_id": "CA-482137",
  "phone": "+14155550142",
  "state": "CA",
  "seconds": 184,
  "payout": "27.50",
  "source": "Northwind Media",
  "click_id": "Cj0KCQjw"
}

seconds is unquoted because {{call.duration}} is a whole number; everything else is quoted because it is a string. Had publisher.name contained a quotation mark, JSON mode would have escaped it and the body would still parse.

When a macro comes out empty

An empty field in a partner's log is almost always one of these, in roughly this order of likelihood:

  • The call genuinely has no such value. {{publisher.name}} on your own media, {{buyer.name}} on an unrouted call, {{call.answered_at}} on a call nobody answered.
  • It is one of the ping-only fields{{caller.city}}, {{caller.zip}}, {{publisher.ref}} — on a call that was dialled rather than won at auction. Nobody sent them, so there is nothing to expand.
  • The tag is not on this call. Check the call's own tags in the call log; a tag captured by DNI only exists if the visitor's session was matched.
  • It is a tag in a URL written by a buyer, where tags are withheld by design.
  • The event fired too early. Money and duration are 0 before settlement. A pixel on call.connected reporting value=0.00 is working correctly and firing on the wrong event.

What it is not is a typo: a misspelt macro is refused when a pixel is saved, so a template that saved has only real macros in it.

Next steps