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
- In Freshsales, click your profile picture (top right) and open Profile Settings.
- Open the API Settings tab.
- 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
| Field | Required | Description |
|---|---|---|
bundle_alias | Yes | Your account subdomain. From https://acme.myfreshworks.com, enter acme. |
api_key | Yes | Your Freshworks CRM API key from Profile Settings > API Settings. |
Configure in Signal
- Go to Integrations > Add Integration > Freshsales.
- Choose the Default variant.
- Enter your
bundle_aliasandapi_key. - Select the consent categories that apply (typically
marketing). - Click Save.
API Endpoint
POST https://{bundle_alias}.myfreshworks.com/crm/sales/api/contacts/upsertAuthentication is a static token in the Authorization header:
Authorization: Token token={api_key}
Content-Type: application/jsonThe 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.
| Signal | Freshsales field | Notes |
|---|---|---|
unique_identifier.emails and contact.emails | The match key. Events without an email are dropped — Freshsales cannot upsert without a unique identifier. | |
| Phone | contact.mobile_number | Normalised to E.164 by Signal before sending. |
| First / last name | contact.first_name, contact.last_name | Required 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 event | Freshsales action |
|---|---|
page | Upsert contact (only when an email is known) |
Signed Up | Upsert contact |
Logged In | Upsert contact |
Lead Generated | Upsert contact |
Order Completed | Upsert 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"
}
}Consent
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
- In Freshsales, go to Contacts.
- Search for the test email.
- 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
| Problem | Solution |
|---|---|
401 / 403 Unauthorized | Check the API key is correct and the owning user can create/edit contacts. The header must be Token token=<key>, not Bearer. |
404 Not Found | The bundle_alias is wrong. Confirm the subdomain in your Freshsales login URL. |
422 on create | Missing first_name / last_name, or the email matched more than one existing contact (a duplicate that needs manual merge in Freshsales). |
| Contact not appearing | The event had no email trait, so it was dropped. Ensure datafly.identify() runs before the tracked event. |
| Phone not saving | Confirm the number is valid and reachable in E.164 form (Signal normalises, but unparseable numbers are skipped). |