CRM

Freshsales ad attribution, with the setup cost stated first

Freshsales is the one CRM Attribi connects to that cannot create its own attribution fields, because Freshworks exposes no API for it. You create sixteen custom fields on the Contact object once, in the admin screens, and Attribi checks them and tells you exactly which are missing rather than half-working. After that it behaves like the others: won deals become offline conversions on every ad platform you have connected, carrying the deal amount in the currency Freshsales recorded it in.

No credit card required · 5-minute setup

What Attribi does with Freshsales

Won deal revenue upload, judged deal by deal

Imports

Every deal is checked against its own stage before any revenue is counted, so the view it happens to sit in never decides the outcome. Tell us which stages mean won and those are definitive. Otherwise Attribi follows the won and lost designation Freshsales keeps on each stage, recognises the ordinary won names, and refuses anything reading as lost or cancelled.

Stops rather than guesses

Imports

If a deal turns up in a stage Attribi cannot read, usually a second pipeline, the sync stops and tells you which setting to fill in. It imports nothing and moves no watermark, so once you answer it picks the whole period back up. A sync that stops is something you can fix. Revenue invented inside your ad platform’s bidding model is not.

The deal’s real currency, not a guess

Imports

Freshsales stores a numeric currency id on the deal rather than a code. Attribi loads your account’s currency list once per run and resolves each deal to its actual ISO currency, so a EUR deal uploads as EUR instead of being flattened into dollars. If a deal’s currency cannot be read, that upload is held back and retried rather than being sent in a guessed one, and if it still cannot be read after a few attempts it is skipped and flagged.

Qualified from a view you name

Imports

Build a filter view in Freshsales for the deals you consider qualified, then give Attribi its exact name. Every deal in that view fires a qualified conversion. Change your mind later and you edit the filter in Freshsales, not a setting here.

Attribution fields on the contact

Sends

Once the fields exist, Attribi fills fourteen of them as leads convert: the visitor id and lead id, the first and last touch source, medium and campaign, the first touch time, the touch count, the full attribution path, the first landing page and referrer, and a link back to the journey in Attribi, so a rep opening a contact can see which campaign produced them.

Click ID recovery without a tracker

Imports

Two of the sixteen fields exist for your own forms to fill with the ad click ID and its type. Attribi never writes those two, it reads them back off the contact when a deal moves, which is what keeps click-level matching alive with no script on your site.

On the roadmap

Planned, not live today. Listed so you can see what this integration does not do yet, before you sign up.

Automatic field creation

Planned Sends

Creating the sixteen fields for you, the way Attribi does for HubSpot, Salesforce, Zoho and Pipedrive. This one is not ours to schedule: it needs Freshworks to publish a stable field-creation API, and the moment they do the rest of the connection already works by field name and needs no change.

Not yet available.

Setting up Freshsales

  1. 1

    Create the sixteen fields in Freshsales

    In Freshsales go to Admin, Fields, Contact, and add the sixteen custom fields Attribi lists. They are the same set every CRM connection uses, prefixed cf_mta_. Make attribution_path, first_landing_page, first_referrer and attribution_url text areas, since a journey path or a tagged landing URL runs past a short text field; the other twelve are single-line text. This needs the Growth plan or above, where the twelve text fields and four text areas fit inside the per-module caps; the Free plan allows no custom fields. Do this before connecting and the next step passes first time.

  2. 2

    Connect with your API key

    Freshsales has no OAuth, so you paste your bundle address, such as acme.myfreshworks.com, and an API key from your Freshsales profile. Choose whether Attribi may create contacts Freshsales does not hold, or should only enrich records you already own. Attribi then verifies the sixteen fields and names any that are missing.

  3. 3

    Confirm which deals get scanned, then name your qualified view

    Attribi scans the first deal view whose name says won and does not say lost. That choice no longer decides what counts as revenue, since every deal is checked against its own stage, but it does decide which deals are looked at, so if you keep several views with won in the title confirm the right one is being read or none of your wins are seen. For mid-funnel, create a filter view of the deals you treat as qualified and type its name exactly as Freshsales spells it. Leave it blank and Attribi reports on won deals only. If your won stages are unusual, or you run more than one pipeline, send us those stage names at onboarding: there is no picker for them in the product yet.

Which Freshsales deal becomes an ad conversion, and what the setup costs

The setup is the longest of any CRM Attribi connects to. The won check is the most careful.

Freshsales ad attribution means reading won and qualified deals once a day over your API key, judging each deal by its own stage rather than the view it sits in, and uploading the amount in the deal's currency to your ad platforms. The cost comes first: Freshworks has no field-creation API, so you build sixteen Contact fields by hand.

How does Freshsales ad attribution work?

Freshsales ad attribution is the practice of tying a deal in Freshsales, the CRM inside Freshworks, back to the ad click that produced its contact, and sending the deal's outcome and amount to the ad platform as an offline conversion. Freshsales keeps a sale as two records: the Contact is the person, the Deal is the money, and they are tied by primary_contact_id or, failing that, the first entry in contacts_added_list. Attribi needs only these fields:

ObjectFieldWhat Attribi does with it
ContactemailFirst match key against existing leads
Contactmobile_number, then work_numberSecond match key, hashed; also a hashed identifier on uploads
Contactcustom_field.cf_mta_clickid, custom_field.cf_mta_clickid_typeRead back for click matching; never written by Attribi
Dealdeal_stage_idThe only evidence of a win Attribi accepts
Dealamount, else base_currency_amountThe conversion value
Dealcurrency_idResolved to an ISO code via GET /api/selector/currencies
Dealupdated_atSort key; the pull stops at the last run's watermark
Dealclosed_dateThe conversion time on a won deal

The data path from a Freshsales Contact and Deal, through Attribi's daily pull, to a Google Ads Data Manager event carrying transactionId, conversionValue, currencyCode and gclid, with the real field names at each step.

Four Freshworks CRM API endpoints do the work: GET /api/deals/filters lists your saved views, GET /api/deals/view/:id?include=contacts pages one view at the documented 25 deals a page with contacts sideloaded, and GET /api/selector/deal_stages and GET /api/selector/currencies are fetched once per run.

Which Freshsales stage counts as won?

Freshworks' deal stages help article says Won and Lost are default stages in every pipeline, a pipeline needs at least three stages including those two, admins customise the rest under Admin Settings, Deals and Pipelines, and a second pipeline is a Pro plan feature. So most accounts have one pipeline ending in Won and Lost, and a minority have several, named whatever the admin chose.

Attribi reads the stage of each deal and decides in this order:

  1. Stages you have told us are won, by id, are the only won stages. Nothing else is consulted.
  2. Otherwise, stage names you have told us are won are matched exactly, trimmed, case sensitive.
  3. Otherwise, the won or lost designation Freshsales keeps on its own stages settles it, in both directions.
  4. Otherwise, an ordinary won name such as Won or Closed Won is recognised, after anything reading as lost, rejected, cancelled or dead has been refused. Closed on its own is not a win, because Closed Lost contains the same word.

A stage that cannot be read, because the stage list did not load or the deal sits in a stage id the list does not contain, stops the whole sync. The second case is almost always a second pipeline, since the stage selector returns the default pipeline's stages.

Six deals inside a saved view named Won and Committed, and the verdict on each: Won and Closed Won become revenue conversions, Agreement Signed is not booked until configured, Closed Lost is refused, a second-pipeline stage stops the sync, and a won deal with no amount sends no revenue.

What broke on 2026-08-07, and what does the fix ask of you?

Until 7 August 2026 the pull took the first saved view whose name contained "won" and not "lost" and treated every deal inside it as closed-won revenue. There was no per-deal check. A view named "Won and Committed" or "Likely to Win" passed, and every deal in it went to Google Ads and Meta as a purchase. Bidding was trained on money that never arrived, and nothing in the product could have told you.

The fix shipped that day (our internal register calls it G44). The view is now only a reading scope, and an unverifiable deal stops the sync before the watermark moves, so once you supply the missing setting the next run re-reads the whole window.

It also added an onboarding step. There is no stage picker in the product for Freshsales, a deliberate scope decision, so three things are set by hand with us:

  • Whether the account has one pipeline. If more, the numeric ids of your won stages, which become closed_won_deal_stage_ids.
  • Any won stage whose name a stranger would not read as a win, which becomes closed_won_stage_names.
  • Which view to read if several have "won" in the title, which becomes closed_won_view_id. A wrong view can now only miss wins, not invent them.

Which Freshsales plan lets you create the sixteen fields?

The earlier copy on this page said the Free plan allowed twenty-five custom fields. That number is on no current Freshworks page. Freshworks' custom field limits article, re-read on 12 October 2026, lists zero custom fields of every type on Free. They begin on Growth, capped per type and per module:

PlanText fields per moduleText areas per moduleTotal per module
Free000
Growth205150
Pro3010250
Enterprise7020630

Attribi needs twelve Text fields and four Text areas on the Contact module. Each is created under Admin Settings, Leads, Contacts and Accounts, on the Contact module, with the plus icon, per Freshworks' custom fields article. Freshsales prefixes a custom field's internal name with cf_, hence the names below.

Create this fieldTypeFilled by
cf_mta_visitor_id, cf_mta_lead_idText fieldAttribi
cf_mta_first_touch_source, cf_mta_first_touch_medium, cf_mta_first_touch_campaign, cf_mta_first_touch_atText fieldAttribi
cf_mta_last_touch_source, cf_mta_last_touch_medium, cf_mta_last_touch_campaignText fieldAttribi
cf_mta_touchpoint_countText fieldAttribi
cf_mta_attribution_path, cf_mta_first_landing_page, cf_mta_first_referrer, cf_mta_attribution_urlText areaAttribi
cf_mta_clickid, cf_mta_clickid_typeText fieldYour form

The four Text areas matter: a contact write fails as a whole when one value overflows, so a short cf_mta_attribution_path would silently stop all fourteen values landing. Attribi clips the four long values at 1,300 characters before sending.

When you click Provision, nothing is created. Attribi reads GET /api/selector/contact_fields, compares the names against the sixteen, and either marks the connection ready or lists the missing names. The code carries a note to replace this with real create calls the day Freshworks publishes a stable field-creation API.

How does the API key work, and what are the limits?

There is no OAuth flow. You paste your bundle address, acme.myfreshworks.com or an older acme.freshsales.io host, and an API key. The API documentation puts the key under your profile picture, Profile Settings, API Settings, and every request carries Authorization: Token token=<your key> against https://<bundle>/crm/sales/api/.... A per-user key dies with that user's deactivation, so connect with a user the company keeps. Rotating it is a paste; settings survive when the bundle alias is unchanged.

The same documentation sets the limit at 1,000 API requests per hour per account, returning HTTP 429 past it. A daily run is the four fixed calls plus one request per page of 25 deals, so a few hundred modified deals is under thirty requests. Each pass is capped at 50 pages, and a run that hits the cap holds its watermark for up to three runs so the oldest deals are not skipped. A deal won this morning is in your ad platforms tomorrow, not within the hour.

What does the pixel-less route look like with Freshsales?

Without the Attribi script on your site, deals are still read. What changes is matching: with no first-party record of the click, Attribi falls back to the hashed email and phone on the Contact, which match less often than a click id.

The two customer-owned fields close that gap. If your form writes the ad click id into cf_mta_clickid and its parameter name, gclid, fbclid or li_fat_id, into cf_mta_clickid_type, Attribi reads the pair back whenever a deal moves and uploads with a real click id. Both must be present; one click id per contact is stored. The pixel-less setup guide covers the form side; the GCLID capture guide covers where the id gets dropped.

Phone numbers should reach Freshsales in international format, because Attribi hashes the digits as stored and a national-format number does not match on Meta. And the pull fires three stage events, a lead event the first time it meets a new contact, then qualified and closed-won, but never the visit history; see pixel-less attribution for what the script adds.

A worked example: from a won deal to a Google Ads upload

The numbers are illustrative. On 1 March a prospect clicks a Google ad for a Dubai fit-out company and submits a form that stores gclid in a hidden field. Freshsales gets a Contact with email, a mobile_number beginning +9715, cf_mta_clickid set to the GCLID and cf_mta_clickid_type set to gclid. A rep opens a Deal, amount 42,000, currency_id pointing at AED, and on 28 March drags it to Won.

At the next daily run Attribi loads the stage and currency lists, pages the saved view, and reaches the deal because its updated_at is past the watermark. The stage resolves to Won, which Freshsales designates as won. currency_id resolves to AED. The email matches no existing lead, so one is created with a synthetic touchpoint carrying the GCLID, a lead-stage event goes out for it, and the deal is mirrored into Attribi's deal ledger.

The closed-won fan-out fires with 42,000 AED. For Google Ads that is one Data Manager API event: transactionId of the lead id plus :closed_won, so a retry can never double count, conversionValue 42000, currencyCode AED, the GCLID as the ad identifier, and the hashed email and phone alongside it. Because Attribi uses the Data Manager API, the 15 June 2026 rule blocking developer tokens with no uploads between 17 December 2025 and 15 June 2026 does not apply.

One number to check first. A conversion action Attribi creates for you today carries a 30-day click-through window. Click on 1 March, win on 28 March: it counts. A deal closed on 10 April would be accepted and credited nothing. The change to 90 days is written but not live, so if deals take more than a month, map an existing action with a longer window, or read what happens when the sales cycle outlasts Google's memory.

What this route does not do

  • It creates no fields. Sixteen by hand, on Growth or above.
  • It has no stage or view picker. Multi-pipeline accounts and unusual won names are configured with us; until then the sync stops rather than guesses.
  • A won deal with no amount, or an amount of zero, is recorded but sends no revenue conversion.
  • A deal whose currency_id is not in your account's currency list has its upload withheld and retried, then skipped with a flag, never sent in a guessed currency. A multi-currency account whose deals can lack a currency needs an account default set by hand. We have not verified the currency selector on a live account; if wrong, uploads are withheld, never mislabelled.
  • The qualified pass depends on a saved view whose name you type exactly. Rename it and qualified detection silently ends.
  • The first run reads 30 days back. Attribi imports no Microsoft spend, so there is no Microsoft ROAS, and funnel graduation measures maturity from Google clicks only.
  • If revenue already lands in Pipedrive or Zoho, connect that instead; they provision in one click.

Common questions about this connection

Can Attribi read won deals from a second Freshsales pipeline? Yes, once the won stage ids for that pipeline are configured. The stage selector returns the default pipeline's stages, so a deal elsewhere arrives with a stage id the list does not contain, and Attribi stops the sync with a message naming the setting rather than guessing. Send the ids and the window is re-read.

How many Freshsales API requests does a daily sync use? Four fixed calls, then one request per page of 25 deals in the won view, and the same for the qualified view if you named one. A few hundred modified deals is well under thirty requests.

Why did the Freshsales sync stop with a message about stage ids? A deal turned up whose stage Attribi could not read, almost always from a second pipeline, or the stage list did not load. Nothing was imported and the watermark did not move. Once the won stage ids are set, the next run picks up from where it stopped.

Does Attribi fill the Freshsales fields for contacts it did not create? Yes. In full mode Attribi finds the contact by email and updates it, or creates it if absent; in attribution-only mode it updates contacts you already hold and skips the rest. The two click id fields are never written in either mode.

Which Freshsales plan do we need before connecting? Growth or above. Freshworks lists no custom fields on Free, and Attribi's sixteen need twelve text fields and four text areas on the Contact module, inside Growth's caps of 20 and 5. Custom pipelines are a Pro feature, which is when the stage id setting becomes relevant.

The takeaway

If your sales team lives in Freshsales and your ads live in Google and Meta, sixteen fields are a fair price for a won check that guesses nothing. Start with 30 days on the Pro plan, create the fields before you connect, and the first won deal reaches your ad account the morning after it closes.

Funnel stages

Stages Attribi supports for Freshsales.

  • Lead
  • Qualified
  • Closed Won

Works with

Frequently asked questions

Why do we have to create the fields ourselves?

Because Freshworks has no documented, stable API for creating custom contact fields with a bundle API key. Rather than ship a provisioner that works on some accounts and silently fails on others, Attribi verifies instead: it reads your current contact schema, compares it to the sixteen fields it needs, and if any are missing it stops and lists them by name with the admin path to create them. Click provision again afterwards and it confirms. HubSpot, Salesforce, Zoho and Pipedrive are all one button, so this is the one connection where the setup is genuinely longer.

Which Freshsales fields does Attribi actually fill in?

Fourteen of the sixteen: visitor id, lead id, first touch source, medium, campaign and time, last touch source, medium and campaign, touchpoint count, attribution path, first landing page, first referrer, and a link to the journey in Attribi. The other two, the click ID and click ID type, are meant to be filled by your own forms and read back by Attribi. Create all sixteen, because the verification step checks for the full set.

Does this work on the Freshsales free plan?

No. Freshworks lists no custom fields of any type on the Free plan, and the sixteen fields are not optional. Growth is the first plan that allows them: twenty text fields and five text areas per module, and Attribi needs twelve of the first and four of the second on Contacts, so the set fits with a little room for your own. Pro and Enterprise have more headroom. The API key you connect with works the same on every paid plan, and there is no OAuth app to approve.

How does Attribi decide a Freshsales deal is won?

By the deal’s own stage, checked one deal at a time. If you have told us which stages mean won, those are the only ones that count and they are matched exactly, which is the setting to use when a stage is named something like Agreement Signed or when you run more than one pipeline. Otherwise Attribi follows the won and lost designation Freshsales already keeps against each stage, and where a stage carries none it recognises the ordinary won names and refuses anything reading as lost, rejected or cancelled. Where none of that settles it, the sync stops with a message naming the setting to fill in rather than booking the deal, and it moves no watermark, so nothing is skipped once you answer. Two more things: there is no stage picker in the product yet, so unusual stages are configured with us at onboarding, and a won deal with no amount is recorded but sends no revenue conversion.

Do we need the tracking script on our website?

Not for revenue. Qualified and closed-won outcomes are read out of Freshsales and uploaded to your ad platforms whether or not any script is installed, and a form that fills the click ID fields keeps click-level matching too. Without the script you lose the visit history: no page views and no multi-touch path, only what Freshsales holds.

Updated

Connect Freshsales to Attribi.

Sign up and connect it from the Connections page.

Start Free

No credit card · Free up to 500 visitors a month