Conversions
Capture high-value outcomes as leads, with the real delivery result, idempotency and attribution.
A conversion is a high-value outcome: a signup, a demo request, a purchase. Unlike a plain event, a conversion is its own record, attributed to the visitor and enriched into a lead. Leads trigger the notification email and your integrations (HubSpot, Hyperflow, Zapier, Brevo).
Capturing a conversion
async function onSubmit(form) {
orwel.identify({ email: form.email, name: form.name });
const { success, reason } = await orwel.conversion('demo_request', {
email: form.email,
company: form.company,
});
if (!success) console.warn('Orwel conversion not delivered:', reason);
showThanks(); // flip the UI only after the await
}conversion() returns Promise<{ success: boolean; reason?: string }>. Callers that ignore it keep working, but awaiting it before you unmount the form makes sure the request is not cut off.
What happens when you call it
- Waits for the visitor id
The SDK waits up to 5 seconds for the visitor id to resolve, so the conversion can be attached to the visitor and their history. If it never resolves, nothing is sent and the result is
{ success: false, reason: 'visitor_unresolved' }. Sending it anyway would create a lead with nobody behind it. - Adds a conversionId
Each conversion gets a client generated UUID. The server deduplicates on it, so a retried or double fired submit creates one lead, not two.
- Attaches attribution
The persisted first and last touch UTMs and ad click ids travel with the conversion. See Attribution.
- Sends now and reports back
The queue is flushed immediately and the promise resolves with the real outcome.
| Result | Meaning |
|---|---|
{ success: true } |
The server acknowledged the conversion. |
{ success: false, reason: 'visitor_unresolved' } |
The visitor id did not resolve in time. Nothing was sent. |
{ success: false, reason: 'delivery_failed' } |
The request was sent but not acknowledged (network, timeout or rejection). |
{ success: false, reason: 'error' } |
Something failed inside the SDK. |
If the input is invalid (code not snake_case, properties not an object, invalid email), conversion() throws instead.
Always send an email
The server looks for an email (or a phone) in the conversion properties or in the visitor's traits from identify(). Without one, the conversion is stored but the lead cannot be enriched, no notification email is sent, and integrations like HubSpot skip it.
Conversion codes
Use snake_case codes that describe the outcome: demo_request, newsletter_signup, contact_form, purchase. Unlike track(), conversion codes are not gated by the workspace catalog, so any valid code is accepted.
If the conversion has a value, pass amount and currency. They are stored with the lead, and a conversion with a payment signal (amount, plan, order_id, or codes like purchase) moves the visitor to the customer stage.
await orwel.conversion('purchase', { email, amount: 499, currency: 'USD', plan: 'growth' });track vs conversion
track |
conversion |
|
|---|---|---|
| Purpose | Any behavioral signal | High-value outcome, lead |
| Returns | { success, eventId } right away |
Promise<{ success, reason? }> after the server answers |
| Code must be in the catalog | Yes | No |
| Stored as | events |
conversions and leads |
| Triggers | Journeys and features | Lead enrichment, notification email, integrations |
Testing
Send conversions with the development key while you build: they are stored as test leads with a Dev badge, and no email or integration fires. Dev mode shows each one with warnings such as a missing email or missing attribution.