Why Push GitHub Signals to Freshworks
Freshworks CRM (Freshsales) is a popular choice for developer-focused sales teams, particularly at companies that also use Freshdesk for support. When a developer stars a GitHub repo or mentions a keyword in a GitHub issue, that is a buying signal — and it belongs in Freshsales where your SDRs can act on it.
GitLeads monitors GitHub in real time, enriches each signal with developer profile data, and pushes a contact record to Freshsales via webhook or direct API. You do not need to write scrapers or poll the GitHub API yourself.
Two Integration Methods
Method 1: Native GitLeads → Freshworks Webhook
GitLeads has a native webhook destination. Configure the destination URL as your Freshsales API endpoint and map the lead fields. No code required for basic use.
Method 2: Custom Integration via GitLeads Webhook + Node.js
For full control — custom field mapping, deduplication, and lead scoring — use the GitLeads webhook to call the Freshsales Contacts API directly:
import express from 'express';
const app = express();
app.use(express.json());
const FRESHSALES_DOMAIN = process.env.FRESHSALES_DOMAIN!; // yourcompany.myfreshworks.com
const FRESHSALES_API_KEY = process.env.FRESHSALES_API_KEY!;
interface GitLeadsPayload {
signal_type: string;
repo?: string;
keyword?: string;
github_username: string;
name: string;
email?: string;
company?: string;
bio?: string;
top_languages?: string[];
followers?: number;
profile_url: string;
}
async function upsertFreshsalesContact(lead: GitLeadsPayload) {
const url = `https://${FRESHSALES_DOMAIN}/api/contacts`;
const body = {
contact: {
first_name: lead.name.split(' ')[0] || lead.github_username,
last_name: lead.name.split(' ').slice(1).join(' ') || '',
email: lead.email,
job_title: 'Software Engineer',
company: { name: lead.company || '' },
custom_field: {
cf_github_username: lead.github_username,
cf_github_signal: lead.signal_type,
cf_signal_source: lead.repo || lead.keyword || '',
cf_top_languages: (lead.top_languages || []).join(', '),
cf_github_followers: lead.followers || 0,
cf_github_profile: lead.profile_url,
cf_bio: lead.bio || '',
},
},
};
const res = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Token token=${FRESHSALES_API_KEY}`,
},
body: JSON.stringify(body),
});
if (!res.ok) {
const err = await res.text();
throw new Error(`Freshsales API error: ${res.status} ${err}`);
}
return res.json();
}
app.post('/webhook/gitleads', async (req, res) => {
const lead: GitLeadsPayload = req.body;
// Only push if email is available (required for Freshsales contact)
if (!lead.email) {
return res.status(200).json({ skipped: true, reason: 'no_email' });
}
try {
const contact = await upsertFreshsalesContact(lead);
console.log('Created Freshsales contact:', contact.contact?.id);
res.json({ success: true, id: contact.contact?.id });
} catch (err) {
console.error('Failed to create contact:', err);
res.status(500).json({ error: String(err) });
}
});
app.listen(3000);Setting Up Freshsales Custom Fields
Before running the integration, create custom fields in Freshsales under Settings → CRM → Custom Fields → Contacts:
- `cf_github_username` — Text field for GitHub handle
- `cf_github_signal` — Text field for signal type (repo_star, keyword_mention)
- `cf_signal_source` — Text field for the repo or keyword that triggered the signal
- `cf_top_languages` — Text field for comma-separated programming languages
- `cf_github_followers` — Number field for follower count (proxy for influence)
- `cf_github_profile` — URL field for direct GitHub profile link
- `cf_bio` — Text/Multiline for raw GitHub bio
Lead Scoring in Freshsales
Use Freshsales Lead Scoring rules to prioritize GitHub developer leads automatically:
- +20 points if `cf_github_followers` > 200 (influential developer)
- +15 points if `cf_github_signal` = keyword_mention (higher intent than passive star)
- +10 points if `cf_top_languages` contains your target stack (e.g., Rust, Go)
- +10 points if email is a company domain (not Gmail/Hotmail)
- +5 points if `cf_bio` contains "founder", "CTO", or "engineer"
Deduplication Strategy
GitHub developers often have multiple signals. Use `cf_github_username` as the dedup key in Freshsales to prevent duplicate contacts:
async function upsertWithDedup(lead: GitLeadsPayload) {
// Search for existing contact by GitHub username custom field
const searchUrl = `https://${FRESHSALES_DOMAIN}/api/contacts/search?q=${lead.github_username}&include=custom_field`;
const searchRes = await fetch(searchUrl, {
headers: { Authorization: `Token token=${FRESHSALES_API_KEY}` },
});
const searchData = await searchRes.json();
const existing = searchData.contacts?.find(
(c: { custom_field?: { cf_github_username?: string } }) =>
c.custom_field?.cf_github_username === lead.github_username
);
if (existing) {
// Update existing contact with latest signal
await fetch(`https://${FRESHSALES_DOMAIN}/api/contacts/${existing.id}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
Authorization: `Token token=${FRESHSALES_API_KEY}`,
},
body: JSON.stringify({
contact: {
custom_field: {
cf_github_signal: lead.signal_type,
cf_signal_source: lead.repo || lead.keyword || '',
},
},
}),
});
return { updated: true, id: existing.id };
}
return upsertFreshsalesContact(lead);
}