Install
openclaw skills install @aaronbeashel/converlySet up server side conversion tracking for ad platforms (Google Ads, Meta, ChatGPT, LinkedIn, TikTok and more) without writing tracking code. Use this whenever the user wants to track form submissions, meetings booked or chats started as conversions, capture GCLID or FBCLID click IDs, fix broken conversion tracking, set up Google Enhanced Conversions or Meta CAPI, check which ads are converting, or connect a form tool like Typeform, Webflow forms, or a custom HTML form to an ad platform, even if they never mention Converly by name.
openclaw skills install @aaronbeashel/converlyConverly tracks form submissions on a website and fires conversion events to ad platforms (Google Ads, Meta, GA4 and more), including server side delivery like Google Enhanced Conversions and Meta CAPI. You do the whole setup from the terminal with the converly CLI, verify it works with a real test event, and read conversion results afterwards.
Every CLI command prints one JSON document to stdout. Exit code 0 means success. Errors are JSON on stderr with the API's error code.
CONVERLY_API_KEY or any token. Authentication is converly login, which happens in the user's browser.converly destinations connect returns a URL the user must open in their browser. Never claim a destination is connected until converly handoffs wait <id> returns "status": "completed". Never skip this step, and never work around it by asking for tokens.converly test-event returning "server_status": "success" proves the DELIVERY half: Converly can reach the ad platform and the platform accepted the conversion. install status showing "detection": "confirmed" proves the loader is on the page and running (it phones home on page load). Neither proves the form itself gets detected. Full proof of capture is a real form submission appearing in converly events list with a successful action status. Report exactly which level you verified. Publishing a flow proves nothing by itself.--allow-real without the user's explicit agreement in this conversation. It reports a real conversion to their ad platform. Ask, wait for yes, then run it."domain": null rejects every event server side. The flow will publish fine and capture nothing. Check converly sites list and fix with converly sites update <site_id> --domain <their-domain> before you publish.flows delete requires --yes), unpublishing a flow that is live, disconnecting a destination, or any DELETE via converly api. Never do these on your own initiative, even for things you created this session.--idempotency-key value so it cannot double-create.converly destinations conversions <destination> first, whatever the platform. If it returns options, show them and ask which one this form represents. If it comes back empty or errors, ask the user what the conversion should be called rather than inventing one. A wrong conversion still reports success while quietly corrupting the numbers they are trying to collect. Google Ads can only fire conversion actions that already exist in their Google Ads account, so if none fit they create one there and you re-list with --refresh. Meta and GA4 take a standard event from the list, or a name of the user's own with --custom.Collect four facts before you build anything. Answer them yourself from the repo or page when you have access; otherwise ask the user, all in one message rather than drip-feeding:
api
trigger instead and say why.Work through these steps in order.
Install the CLI if it is missing:
npm install -g @converly/cli
(Or run every command through npx @converly/cli ... without installing.)
Then check where you stand:
converly whoami
not_logged_in, run converly login and tell the user a browser window will open for them to log in. If they do not have a Converly account yet, run converly login --signup instead. Signing up starts a free trial automatically, no card needed. Wait for the command to finish, then re-run converly whoami.converly login --device. It prints a short code and a URL the user opens on any device (their phone is fine); they approve there and the CLI picks the credential up. The CLI selects this automatically when no browser is available.CONVERLY_API_KEY is set in the environment, the CLI uses it automatically for Converly's own deployments. If commands then fail saying the key was rejected, the key is bad. Ask the user to replace or unset it; do not try to work around it.At any point, converly status prints the ordered setup checklist for a site: what is done, what needs action, and the exact command for each. Run it whenever you are unsure what comes next.
converly sites list
Every account has a default site. Use it unless the user is tracking a second website. If domain is null, set it now (hard rule 5):
converly sites update site_XXXX --domain example.com
Use the site's real public domain. One domain covers both the apex and its www variant.
The trigger is the form tool on the user's website. Get the catalogue:
converly triggers
Match the user's form tool to a slug in providers[] (for example webflow-forms, gravity-forms, hubspot-forms). For a hand coded HTML form use generic-form. If you have repo access, identify the form tool from the code instead of asking.
Custom sites are the one case where generic-form is not automatically the right answer. Detection is dependable for a known form tool, whose markup and submit behaviour Converly knows. A hand coded form on a Next.js, Astro, SvelteKit or Remix site varies far more, and many post to a backend route or server action rather than submitting the classic way. So when you are working in the site's code and the form posts to a backend, the api trigger is often more reliable. The backend confirms the conversion directly instead of relying on the browser catching a bespoke form, and you are already in the code, which is the part a human would find fiddly.
Put the call where the handler (route handler, server action, or whatever processes the submission) has confirmed the submission actually succeeded, not at the top of it. Every call to the webhook counts a conversion, so firing before validation would count failed and spam attempts too. Ask the user before editing their code.
Use generic-form when the form is genuinely simple, you do not have code access, or the user prefers no code changes.
Check the provider's setup block before using its slug. Most tools have requires_connection: false and their slug goes straight into flows create. A few (Typeform, Jotform, Calendly, Acuity Scheduling today, read the data, never a memorized list) have requires_connection: true. Those must be connected FIRST, or the flow tracks nothing properly:
converly triggers connect typeform --site site_XXXX
Give the user the returned url (they sign in to the tool there, or paste its API key), then confirm completion the same way as a destination:
converly handoffs wait hdf_XXXX
When the completed result lists anything in user_steps_remaining, the connection alone is NOT the whole setup. Relay each step to the user and treat setup as unfinished until they confirm it is done. Acuity is the standing example. Its bookings are only reported once the user pastes a tracking snippet inside Acuity's own settings (the connect window shows them the snippet). Converly cannot verify that step happened, so never report Acuity setup as complete on connection alone; the proof is the first booking appearing in converly events list.
The same honesty applies to capture_without_connection in the setup block. It tells you what actually happens if the user declines to connect (for example Typeform still reports bare submission counts but no visitor details, so ad platforms match poorly). Use it to explain tradeoffs truthfully, not to skip the connect step.
Once a connection-required platform is connected, you can narrow the flow to one specific form or event type instead of firing on all of them. List the account's real ones with converly triggers options <slug> --site site_XXXX, offer the user a specific value, and write it back as trigger_config.conditions (the full shape is in references/triggers.md → Filters). This is optional, an unfiltered flow is valid and publishable. Browser-detected form tools do not support this, narrow those by page with --pages instead.
Before choosing a trigger type at all, apply fact 3 from the interview. If there is a verification step, a payment, or onboarding between the form and the real outcome, a form trigger counts people who never converted; use the api trigger instead.
The api trigger has no form tool to detect, so its setup is different. Connect it with converly triggers connect api --site site_XXXX, which hands back a webhook URL and secret that the user's backend calls when the conversion is confirmed. For a Node backend, the @converly/sdk-node package does the signed call in a few lines (npm install @converly/sdk-node, then createClient and completeSignup). Other stacks call the webhook directly. The full backend guide is at developers.converly.io/api-trigger. This needs code access, so it suits agents working in the site's repo.
See what is available and what is already connected:
converly destinations list
First check whether this destination needs a connection at all. In the destinations types output, a destination with an empty connection_types list is browser side (Microsoft Ads, X Ads, Pinterest, Snapchat, the analytics pixels). There is nothing to connect, so skip the connect commands here and go straight to choosing the conversion below. Details in references/destinations.md.
For connected destinations (google-ads, meta, google-analytics, linkedin-ads, tiktok-ads, reddit-ads) showing "connected": false:
converly destinations connect google-ads --site site_XXXX
Give the user the returned url and say: "Open this link in your browser to connect your Google Ads account." Then poll until they finish:
converly handoffs wait hdf_XXXX
This blocks up to 10 minutes; the link itself is valid for 30. If the wait times out while the link is still valid, re-run handoffs wait with the same id rather than creating a new link (the error message tells you which case you are in). Destinations are account wide, so one connection serves every flow.
Now choose the conversion, and let the user choose it (hard rule 8). The connect step above only links the account. It does not decide which conversion gets fired, and that decision is the user's.
First see what this destination needs, then always TRY to fetch its real options:
converly actions <destination>
converly destinations conversions <destination>
Always run the conversions command, whatever the destination. Which platforms serve a list is not something you can infer from the field schema, so ask the API rather than assuming. Three outcomes:
--refresh.Meta alone returns around seventeen standard events (Lead, Contact, Schedule, Subscribe, CompleteRegistration and more), so choosing "Lead" yourself is a guess, not a default. Per platform specifics are in references/destinations.md.
--refresh.--custom so the platform is told it is a custom event.Google Ads takes the conversion id:
converly flows create --site site_XXXX --name "Demo requests" \
--trigger generic-form --destination google-ads --conversion-id 123456789
Meta and GA4 take the event name (say the user picked Contact):
converly flows create --site site_XXXX --name "Demo requests" \
--trigger webflow-forms --destination meta --event-name Contact
The simple form above sets the custom-event flag for you. If you build the action with --json instead, include conversion.is_custom yourself or the flow will fail validation.
Add --value 50 --currency USD if the user wants a conversion value. Restrict to specific pages with --pages /contact,/demo. For a connected platform (Typeform, Jotform, Calendly, Acuity) you can narrow to one real form or event type. List them with converly triggers options <slug> --site <id>, then set trigger_config.conditions via --json. For anything richer (multiple actions), pass the whole flow body with --json; run converly actions <destination> to see each destination's config schema.
Then validate and publish:
converly flows validate flow_XXXX
converly flows publish flow_XXXX
validate returns problems[] (blockers, fix before publishing) and warnings[] (site readiness, for example site_missing_domain). Take warnings seriously, they are the "publishes fine, captures nothing" cases.
converly install snippet site_XXXX
Returns the <script> tag.
<head> of every page yourself and deploy it. Say what you changed.<head> of every page, or into their site builder's custom code slot (Settings → Custom Code in Webflow / Wix / Squarespace, a header scripts plugin in WordPress). Site builders need the site republished before the change is live.Check with converly install status site_XXXX. The loader phones home when a page loads, so the check is quick. Have any page of the site opened once in a real browser, then re-run the command. If you have browser tools, open the page yourself. Read detection:
"confirmed" means the loader has been seen running on the site (or a conversion was already captured). If the response carries origin_authorized: false, the loader runs but the site's domain does not match, so conversions get rejected. Fix the domain with converly sites update and have the page reloaded."never_seen" means the loader has never phoned home. If a page has been loaded since installing and it still says this, the snippet is probably not live yet (site builders need a republish). Two blind spots never send heartbeats: sites installed through the Converly Webflow app, and visitors with privacy signals or unconsented cookie banners. For those, prove the install with a real submission appearing in converly events list (hard rule 3).Verification has two halves. Do both when you can, and say exactly which you did.
Delivery (always do this):
converly test-event --flow flow_XXXX
This fires a test conversion through Converly's server to the ad platform and returns the platform's response. "server_status": "success" proves the connection, the flow config, and the conversion mapping all work. For Meta pass --meta-code (from Meta Events Manager → Test events), for Reddit --reddit-id, for TikTok --tiktok-code, and the test then stays out of real data. Google Ads, GA4, LinkedIn and ChatGPT Ads have no sandbox mode, so the command will refuse with would_create_real_conversion. For those, either get the user's explicit OK to send one real test conversion and re-run with --allow-real (hard rule 4), or skip the test event and verify with a live form submission instead.
Capture (proves the website half): converly install status site_XXXX showing "detection": "confirmed" proves the loader is running on the site (load a page once to trigger it). The form itself is proven by a real submission: ask the user to submit the form once, then find it in converly events list with a successful action status.
Report accordingly. After delivery only: "Delivery to [platform] is verified with a test conversion. The final check is one real form submission, which will appear in the conversion log." After both: "Tracking is verified end to end."
This is the ongoing value. When the user asks "did my ads convert", "is tracking still working", or "who converted this week":
converly events list --limit 20
converly events list --since 2026-08-01T00:00:00Z --status failed
converly events get evt_XXXX
events list is a bounded snapshot of the most recent events (max 100, no paging). Narrow with --since, --flow, --email or --status rather than trying to page. events get shows the per destination delivery result for one event, which is where you look when a conversion did not reach a platform.
site_key vs site id. site_1VQH84sr (8 chars, in the snippet URL) is not the same as site_29EtXn... (22 chars, the API id). Commands take the API id.meta, not meta-ads. The API rejects meta-ads.flows create flags handle this. If you build --json yourself, value and currency go inside the conversion object, and the enhanced conversions toggle is top level enhancedConversions. Copy the config_example from converly actions <destination>.flows update, the old version keeps running until you flows publish again.events list is a bounded snapshot of recent events, max 100, no paging. Narrow with --since, --flow, --email instead of trying to page. An empty filtered result with "complete": false does not prove the conversion never happened.entitlement_required means the trial expired or billing lapsed. The user fixes this at app.converly.io → Settings → Billing. Do not retry around it.publication_in_progress (409) means another publish is mid flight. Wait a few seconds and retry once.converly api GET /flows?limit=5 sends raw requests to the REST API.Every line below is one complete, runnable command, in roughly the order you need them.
converly login --signup # browser login, trial auto starts on signup
converly login --device # headless, approve from any device
converly whoami # account, subscription, sites
converly status # the ordered setup checklist for a site
converly sites list
converly sites update <site_id> --domain example.com
converly triggers # form tool slugs
converly triggers connect <source> --site <site_id> # requires_connection providers only
converly triggers options <source> --site <site_id> # that platform's real forms / event types
converly destinations types # catalogue incl. connection_types
converly destinations list # what's connected
converly destinations connect <type> --site <site_id>
converly handoffs wait <hdf_id> # block until human finishes OAuth
converly destinations conversions <type> # the conversions to OFFER the user
converly actions <type> # action config schema
converly flows create --site <site_id> --name "X" --trigger <slug> --destination <type> --conversion-id <id>
converly flows validate <flow_id>
converly flows publish <flow_id>
converly install snippet <site_id>
converly install status <site_id>
converly test-event --flow <flow_id> # verify destination delivery
converly events list --limit 20 # the conversion log
converly events get <evt_id> # per destination delivery detail