Lead-form ads are served through the same
gravity.getAds() request as
every other ad. You don’t request them differently — when an advertiser runs a
lead-form campaign and it wins the auction, the ad object you receive simply
carries an extra leadForm envelope. Render it and you’re done.How it works
Request ads as usual
Call
gravity.getAds(req, messages, placements) exactly as you do today. No
new parameters. See Request ads.Engine attaches the form
If the winning campaign is configured as a lead-form ad unit, the engine
attaches a
leadForm envelope (LeadFormConfig) to that ad in the response.
Regular campaigns don’t carry it.SDK renders the form
<GravityAd /> detects ad.leadForm and renders the form (or a trigger that
opens it). It fires the impression pixel on visibility, same as any ad.User submits → engine stores the lead
On submit, the SDK
POSTs the values to leadForm.submit_url (the engine’s
/api/v1/lead). The engine hashes PII, stores the lead, and fires a
conversion event for attribution + CPA billing. The SDK shows a success state
(and optionally follows success_redirect_url).Render with <GravityAd />
The pre-built component handles everything. Two extra props are required for
lead-form ads compared to a normal click-through ad — sessionId and
apiKey:
GravityAd renders a normal
click-through when ad.leadForm is absent and the lead form when it’s present.
The same component handles both, so a single integration covers every campaign
type an advertiser might run.
Lead-form props on <GravityAd />
| Prop | Type | Description |
|---|---|---|
sessionId | string | Required for lead forms. Must match the ad-request session id. |
apiKey | string | Bearer key for the submission POST. Falls back to process.env.GRAVITY_API_KEY. |
userId | string | End-user id on your side. Stored with the lead for attribution. |
userEmail | string | Used to pre-fill an email field when the campaign opts into prefill.email. The user can still edit it. |
userPhone | string | Used to pre-fill a phone field when the campaign opts into prefill.phone. |
GravityAd props (slotProps, showLabel, onImpression,
className, …) work as documented in Show ads.
Display modes
The advertiser picks how the form presents itself per campaign vialeadForm.display. The SDK default is modal-bottom-right.
| Mode | Behavior |
|---|---|
modal-bottom-right | The ad trigger sits in your slot; clicking it opens the form as a floating card pinned to the bottom-right. Default. |
modal-center | Clicking the trigger opens a centered, full-screen overlay. |
inline | A compact email-only ask renders directly in your slot — no click step. Best with email pre-fill. |
displayOverride if a mode fits your
UI better:
Identity pre-fill
If you already know the signed-in user’s email or phone, passuserEmail /
userPhone. When the campaign opts in (leadForm.prefill.email /
leadForm.prefill.phone), the SDK seeds the first matching field so the user can
submit in one tap. Pre-fill is advisory — if you don’t pass the value, the form
just renders blank.
With display: "inline" and an available pre-fill value, the SDK skips the
trigger entirely and shows the compact ask immediately.
Render directly with <GravityLeadFormAd />
<GravityAd /> delegates to <GravityLeadFormAd /> under the hood. Use it
directly when you want lead-form-specific controls — openOnMount,
displayOverride, submission callbacks, or a custom success panel:
| Prop | Type | Description |
|---|---|---|
ad | AdResponse | null | The ad from gravity.getAds(). |
sessionId | string | Required. Attribution key; must match the ad request. |
apiKey | string | Bearer key for the submission. Falls back to GRAVITY_API_KEY. |
userId, userEmail, userPhone | string | Attribution + pre-fill (see above). |
displayOverride | 'inline' | 'modal-center' | 'modal-bottom-right' | Override the campaign’s display mode. |
openOnMount | boolean | Render the form immediately instead of an ad card + CTA. Default false. |
extraFields | object | Extra metadata merged into the submission payload. |
submitUrl | string | Override the POST target. Defaults to ad.leadForm.submit_url. |
onSubmitted | (leadId, values) => void | Fires on a successful (2xx + leadId) submission. |
onSubmitError | (err) => void | Fires when the submission fails. |
onDismiss | () => void | Fires when the user closes the form. |
successContent | ReactNode | Replaces the form after a successful submit. |
slotProps | object | Per-element style/class overrides (see Styling). |
Styling
Every lead-form element ships with a stablegravity-lf-* class and the
component exposes --gravity-leadform-* CSS variables, so you can theme the form
to match your chat surface without touching component internals.
gravity-lf-trigger, gravity-lf-overlay,
gravity-lf-card, gravity-lf-header, gravity-lf-title,
gravity-lf-description, gravity-lf-inline-ask, gravity-lf-field,
gravity-lf-input, gravity-lf-consent, gravity-lf-submit, and
gravity-lf-close.
The submission endpoint
You only need this section if you’re not using the SDK components — they do all of this for you. The formPOSTs to leadForm.submit_url (the engine’s
/api/v1/lead).
| Field | Required | Description |
|---|---|---|
sessionId | Yes | Attribution key. Must match the ad request that served this ad. |
campaignId | Yes | The campaignId from the served ad. Echo it back verbatim. |
fields | Yes | Submitted values keyed by LeadFormField.name. |
userId | No | End-user id on your side. |
placement / placementId | No | Echo from the served ad for slot-level reporting. |
consent | No | Required only when the form rendered a consent checkbox (consent_text). |
HTTP 200 with { "status": "ok", "leadId": "..." }. Treat any
other status as a failure and keep the form open — the SDK does exactly this, and
only fires onSubmitted / follows success_redirect_url when it gets a 200
with a leadId.
| Status | Meaning |
|---|---|
200 | Lead stored. Body: { status, leadId }. |
400 | unknown_campaign or campaign_not_configured — the campaign isn’t a lead-form ad unit. |
401 | Missing/invalid bearer key. |
403 | Request origin/referer doesn’t match the publisher’s registered domain. |
503 | Transient server error or timeout — retryable; keep the form open. |
Attribution & billing
A submitted lead is recorded as a conversion, linked to the session, impression, and campaign. If the advertiser runs the campaign on a CPA basis, that conversion is what they’re billed for — so lead-form ads monetize on captured leads, not just clicks. Nothing extra is required on your side: the impression pixel fires on view (as with any ad) and the conversion fires on submit.The leadForm envelope
For reference, this is the shape attached to ad.leadForm when a lead-form
campaign wins. The advertiser configures these values on their campaign; you only
ever read them.
FAQ
Do I have to change my ad request?
Do I have to change my ad request?
No. Lead-form ads come back on the same
gravity.getAds() call. The only
change is rendering: pass sessionId and apiKey to GravityAd so it can
render and submit the form.What if I don't pass sessionId or apiKey?
What if I don't pass sessionId or apiKey?
Without
sessionId, an ad that would have been a lead form renders as a
normal click-through (you keep the impression and click). With sessionId
but no apiKey, the form renders but submission 401s. Pass both for the full
experience.Is raw PII exposed?
Is raw PII exposed?
The user types their details directly into the form on your page; on submit
they go to Gravity over HTTPS, where the engine hashes PII at rest. Use the
same
apiKey you already use for ad requests.Can I render the form myself?
Can I render the form myself?
Yes — read
ad.leadForm, render your own inputs, and POST to
leadForm.submit_url with the payload shape above. Only show success on a
200 containing a leadId.Next
Show ads
The full rendering reference for
GravityAd variants and slot styling.Request ads
How the ad request works — the same call serves lead-form ads.

