IntegrationsCRMFreshsales

Freshsales (Freshworks CRM)

Datafly Signal keeps your Freshsales CRM in sync by creating and updating contacts server-to-server using the Contacts Upsert API. When a known visitor identifies or completes a key conversion on your site, Signal upserts a contact in Freshsales — matched on email — with no browser pixel and no client-side JavaScript.

Prerequisites

Before configuring Freshsales in Signal you need a Freshsales (Freshworks CRM) account, your account subdomain, and an API key.

Find your bundle alias (subdomain)

Your Freshsales URL looks like https://acme.myfreshworks.com. The part before .myfreshworks.com — here acme — is your bundle alias. Signal uses it to build the API host.

Generate an API key

  1. In Freshsales, click your profile picture (top right) and open Profile Settings.
  2. Open the API Settings tab.
  3. Copy Your API Key (a long alphanumeric token).

The API key inherits the permissions of the user it belongs to. Use a dedicated integration user with permission to create and edit contacts so the key can be rotated without affecting a person’s login.

Configuration

FieldRequiredDescription
bundle_aliasYesYour account subdomain. From https://acme.myfreshworks.com, enter acme.
api_keyYesYour Freshworks CRM API key from Profile Settings > API Settings.

Configure in Signal

  1. Go to Integrations > Add Integration > Freshsales.
  2. Choose the Default variant.
  3. Enter your bundle_alias and api_key.
  4. Select the consent categories that apply (typically marketing).
  5. Click Save.

API Endpoint

POST https://{bundle_alias}.myfreshworks.com/crm/sales/api/contacts/upsert

Authentication is a static token in the Authorization header:

Authorization: Token token={api_key}
Content-Type: application/json

The endpoint matches on a unique identifier (email) and either creates a new contact (201 Created) or updates the existing one (200 OK).

Identity Signals

Freshsales is a CRM, not an ad platform, so it matches on raw (unhashed) identifiers rather than hashed audience keys.

SignalFreshsales fieldNotes
Emailunique_identifier.emails and contact.emailsThe match key. Events without an email are dropped — Freshsales cannot upsert without a unique identifier.
Phonecontact.mobile_numberNormalised to E.164 by Signal before sending.
First / last namecontact.first_name, contact.last_nameRequired by Freshsales when a contact is first created.
⚠️

Freshsales requires first_name and last_name to create a contact. If an identify call provides an email but no name, the create will be rejected. Pass full name traits via datafly.identify() for new contacts.

How to send contact data

Call datafly.identify() when a user logs in, registers, or submits a form:

datafly.identify("user-123", {
  email: "jane.doe@example.com",
  first_name: "Jane",
  last_name: "Doe",
  phone: "+44 7700 900123",
  job_title: "Head of Growth",
  company: "Acme Ltd",
  city: "London",
  country: "GB"
});

Signal maps these traits to native Freshsales contact attributes and normalises the phone number to E.164 before delivery.

Event Mapping

The Default preset upserts a contact on the following Signal events (any event lacking an email trait is dropped):

Signal eventFreshsales action
pageUpsert contact (only when an email is known)
Signed UpUpsert contact
Logged InUpsert contact
Lead GeneratedUpsert contact
Order CompletedUpsert contact

All five map to the same upsert call — Freshsales has no event-name concept on this endpoint, so each delivery simply creates or refreshes the contact record. To change which events sync, edit the integration’s Field Mappings in the Management UI.

Example: Lead Generated

Datafly.js call:

datafly.identify("lead-789", {
  email: "sam.lee@example.com",
  first_name: "Sam",
  last_name: "Lee",
  phone: "+1 415 555 0142",
  company: "Globex"
});
 
datafly.track("Lead Generated", {
  value: 0,
  currency: "USD"
});

Freshsales upsert payload sent by Signal:

{
  "unique_identifier": {
    "emails": "sam.lee@example.com"
  },
  "contact": {
    "emails": "sam.lee@example.com",
    "first_name": "Sam",
    "last_name": "Lee",
    "mobile_number": "+14155550142",
    "company_name": "Globex",
    "subscription_status": "subscribed"
  }
}

When you classify a CMP category as marketing in Signal Settings, the canonical marketing-consent flag is mapped to the contact’s subscription_status (subscribed when granted, unsubscribed when denied), so your CRM reflects the visitor’s marketing preference.

Testing Your Integration

Trigger an identify

On a test page, call datafly.identify() with a test email plus first and last name, then fire one of the mapped events (e.g. Signed Up).

Confirm in Freshsales

  1. In Freshsales, go to Contacts.
  2. Search for the test email.
  3. Confirm the contact was created (or updated) with the name, phone, and other traits you sent.

Inspect the payload

Use Signal’s event debugger to view the exact JSON sent to contacts/upsert and the response status (201 create, 200 update).

Troubleshooting

ProblemSolution
401 / 403 UnauthorizedCheck the API key is correct and the owning user can create/edit contacts. The header must be Token token=<key>, not Bearer.
404 Not FoundThe bundle_alias is wrong. Confirm the subdomain in your Freshsales login URL.
422 on createMissing first_name / last_name, or the email matched more than one existing contact (a duplicate that needs manual merge in Freshsales).
Contact not appearingThe event had no email trait, so it was dropped. Ensure datafly.identify() runs before the tracked event.
Phone not savingConfirm the number is valid and reachable in E.164 form (Signal normalises, but unparseable numbers are skipped).