LinkedIn Lead Gen Form hidden fields: tag each lead with its variant
A LinkedIn Lead Gen Form allows up to 20 hidden fields. Keep your own index-to-variant table from the Sume bulk queue so every lead traces to one creative.

LinkedIn's Lead Gen Form specifications list a Settings section where you can add up to 20 hidden fields. If you run many creative variants, plan one of them to carry a variant id so each lead can be traced back to the creative that produced it. This post covers only the Sume side of that: how to keep a reliable index, variant id and run id table from a bulk queue. What a hidden field holds and how LinkedIn fills it is on LinkedIn's page, which I did not find a value source on, so read it before you build.
The reason to do the mapping yourself: Sume's bulk-run docs say the input object is caller data the Format reads, and that your own reference data in it does not come back in output. The queue does give you a stable index for each item and a run_id once the item starts.
What the queue gives you to join on
Each item in the queue receipt has an index (the zero-based position you submitted) and a run_id after dispatch, so a join key exists without Sume echoing anything back.
| Field | Source | Use |
|---|---|---|
Item index | Sume queue receipt | Zero-based position in the submitted items array |
Item run_id | Sume queue receipt | null while queued; read the child at GET /v1/format-runs/{run_id} |
| Hidden fields | LinkedIn Lead Gen Form | Up to 20 additional fields |
Write the mapping before you submit
The script below fixes the variant ids first, submits the queue, and saves a CSV that pairs each index with its variant id and run id. Replace acme/offer-ads with a Format you own.
import csv, os, uuid, requests
API = "https://api.sume.com"
H = {"Authorization": f"Bearer {os.environ['SUME_API_KEY']}"}
variants = ["hook-a", "hook-b", "hook-c"]
items = [{"instruction": f"Offer image, hook {v}", "input": {"variant_id": v}}
for v in variants]
r = requests.post(f"{API}/v1/formats/acme/offer-ads/bulk-runs",
headers={**H, "Idempotency-Key": str(uuid.uuid4())},
json={"concurrency": 3, "items": items}, timeout=30)
r.raise_for_status()
q = r.json()["data"]
with open("variant-map.csv", "w", newline="") as f:
w = csv.writer(f)
w.writerow(["queue_id", "index", "variant_id", "run_id"])
for it in q["items"]:
w.writerow([q["id"], it["index"], variants[it["index"]], it["run_id"]])Keep the table current
A run_id can be null for queued items on the first receipt. Poll the queue's status_url and rewrite the CSV once the items have started, or join on index alone.
- Store the queue id with every row; the API has no list-queues endpoint.
- Never reuse a variant id for a different creative inside one campaign.
- If you retry an item, record the new run id next to the same variant id.
Sources
Related posts
More in Use cases
- LinkedIn Lead Gen Form image 552x200: make it from a 3:1 render
LinkedIn recommends a 552px by 200px form image. Sume has no 552:200 ratio, so ask Ideogram for 3:1, center-crop about 8% of the width, and resize.
- LinkedIn video ad intro text 150 characters, headline 70: test copy
LinkedIn video ads show 150 characters of intro text and a 70-character headline. Test copy on one rendered clip and save Sume jobs for hook variants.
- Two listing photos, one room transition: first and last frame
Use two listing photos as first and last frame of a 6-second Gemini Omni Flash 1.1 clip on Sume: $0.75 at 720p, $1.125 at 1080p. Call and checks.
- Live captions for a 90-minute match: streaming STT, then Sume replay
A 90-minute match costs about $0.81 on MAI-Transcribe-2-Streaming. Sume STT works on recordings: $0.91 after the match for replay captions.
Written by Sume