Goal conversion pixel recipes
Apixel_fire goal fires when a third-party page (your checkout “thank you”, your signup confirmation, your partner’s success screen) renders a signed <img src>. This page walks the full cycle on the wire — mint the signature, embed the pixel, probe it with curl, close the conversion loop, then covers the short-link redirect (GET /l/{code}) in the same frame. The public pixels, redirects, and short links concept page is the contract; the Goals API reference is the per-endpoint shape.
Nothing here ships an API key into a public URL. The <img> hits an anonymous, root-mounted endpoint and authenticates purely with the HMAC in the query string.
Task index
1. Create a pixel_fire goal
cURL
pixel_url is the template you fill in:
data.id — every sig below signs it. The Goals surface in the dashboard shows the same template beside the goal, so an operator can copy it without reading the API.
2. Mint the HMAC sig
The pixel URL’ssig is a hex HMAC-SHA256 over the pipe-joined tuple goalId:contactId:tenantSchema. The tenant schema is part of the signed payload — a leaked URL can’t be replayed under a different schema by swapping the ?schema= param.
Mint server-side (never in a browser bundle — the signing key is your DEVOTEL_API_SECRET_KEY, the same key the API client secrets derive from):
pixel_url template. The Node SDK and the dashboard’s Goals → embed panel wrap the same call — the hash helper reads simpler here than a one-line import.
3. Embed the pixel
On the checkout or signup “thank you” page template — the page a merchant already renders after the order posts — drop the tag with the minted sig spliced in:200 on every render — a forged or malformed sig doesn’t break the page and doesn’t render a broken-image glyph on the buyer’s screen. It just doesn’t record.
4. Probe the pixel and close the loop
Two curl steps — verify the GIF contract, then read the goal’s/conversions listing to confirm the row landed:
cURL
cURL
metadata.source: "pixel" is the tell that the loop came through GET /g/ and not a webhook/manual call. A forged probe — a sig that doesn’t match goalId:contactId:schema — still answers 200 with the GIF, but the /conversions listing shows nothing for it. That asymmetry is the whole security story.
5. Short-link redirect probe
A short link (POST /api/v1/links returns one) resolves anonymously through the same root-mounted surface. Probe it the way a browser would — ask for HTML:
cURL
Accept: text/html and see the friendly page; your probe sees the envelope:
6. Replay and abuse notes
- The signature binds
goalId + contactId + tenantSchema. A replay against a different contact or a swapped?schema=fails verification. Leaking a pixel URL exposes exactly one goal+contact conversion — never the class. - Per-IP rate limits. The pixel families ride at 120 requests/IP/minute;
/l/short links at 60. A loop that fans your own pixel out from one address throttles itself. - Idempotent. Re-rendered pixels don’t double-write; the
/conversionslisting collapses to the first hit. First-event-wins — a replayed GET can’t corrupt the goal’s totals. - Fail-closed on any tamper. No
sig, noschema, malformed schema, mismatched HMAC — all answer200+ GIF and record nothing. The GIF always comes back.
7. Sandbox flavor
Run every step here withdv_test_sk_… before you trust the sig-minting code against live. A sandbox key records sandbox conversions on the same wire, so you can prove the full cycle — mint, embed, curl, verify — without touching a real goal. One invalid-sig branch is the check that matters: send a sig of all zeros and confirm the /conversions listing stays empty while the GIF still returns.