CRM integration
If you run a B2B or SaaS partner program, the conversion that actually matters usually isn’t a self-serve signup — it’s a state change in your CRM. A lead becomes “Sales Qualified”. An opportunity hits “Closed Won”. A trial converts to paid. Without that signal, partners only get paid on the trivial cases and abandon your program.
This doc shows how to wire HubSpot, Salesforce, Pipedrive, or any CRM into OpenPartner so those state changes accrue commission.
The model
OpenPartner already accepts arbitrary conversion events at
POST /attribution/events. To CRM-ify it:
- Mint a scoped API key (
events:write) under Admin → CRM integration. One key per integration. - Configure your CRM (or a Zapier / Make workflow in front of it) to
POST to
/attribution/eventswhen the relevant state change fires, with the API key as a Bearer token. - OpenPartner ties the event to the originating click via the
userIdyou assign at signup time, then accrues commission per the bound campaign’s rule.
The key is scoped — leaked, it can fabricate conversion events for your tenant but can’t edit partners, payouts, or campaigns. Worst case you revoke + re-mint.
The payload
curl -X POST https://your-instance/api/attribution/events \ -H "Authorization: Bearer <your-key>" \ -H "Content-Type: application/json" \ -d '{ "userId": "user_or_email_from_your_system", "type": "opportunity_won", "value": 4500.00, "currency": "USD", "metadata": { "deal_id": "0061k00000ABC", "stage": "Closed Won" } }'Fields:
userId(required) — the same identifier you assigned at signup. Without this OpenPartner can’t stitch the event to a click. If your CRM only knows the contact’s email, use email here as long as you’ve been callingidentify(email)at signup time too.type(required) — any string. Use whatever your team will recognize:opportunity_won,trial_converted,lead_qualified,expansion_signed. The string flows through to the Event row and is visible in your dashboard.value(optional) — decimal amount. Used by percent-based commission rules to compute the partner’s cut.currency(optional, defaults to USD) — ISO 4217 code.metadata(optional) — free-form JSON. Useful for attaching the source CRM record ID so you can audit later.ts(optional) — ISO timestamp of when the conversion happened. Defaults to now. Use this if your webhook fires async and you want the original event time preserved for attribution-window math.
HubSpot
HubSpot Workflows can POST custom JSON via the Custom code (Node.js) action. The path:
- Create a workflow triggered on the deal stage change you care about
(e.g.
Deal stage = Closed Won). - Add a Custom code (Node.js) action.
- Paste:
const fetch = require('node-fetch');
exports.main = async (event, callback) => { const deal = event.inputFields; const response = await fetch('https://your-instance/api/attribution/events', { method: 'POST', headers: { 'Authorization': 'Bearer <your-openpartner-key>', 'Content-Type': 'application/json', }, body: JSON.stringify({ userId: deal.contact_email, // map to the identifier you used at signup type: 'opportunity_won', value: parseFloat(deal.amount), currency: deal.deal_currency_code || 'USD', metadata: { hubspot_deal_id: deal.hs_object_id, stage: deal.dealstage, }, }), }); callback({ outputFields: { status: response.status } });};- Map the workflow’s input fields (
contact_email,amount,hs_object_id,dealstage) so the code can read them.
Salesforce
Salesforce doesn’t have inline custom code in standard Process Builder / Flow, so the cleanest path is Salesforce → Zapier → OpenPartner:
- Create a Zapier zap with Salesforce trigger (e.g. “New Opportunity updated, Stage = Closed Won”).
- Add a Webhooks by Zapier → POST action with:
- URL:
https://your-instance/api/attribution/events - Payload type: JSON
- Headers:
Authorization:Bearer <your-key>Content-Type:application/json
- Data: Map Salesforce fields to
userId,type,value, etc.
- URL:
For high-volume programs (>100 events/day), an Apex trigger calling
HttpCallout directly is more cost-efficient than Zapier — but Zapier
is fine to start.
Pipedrive / Close / Copper
All three have webhook-out features. Configure the webhook to point at
/api/attribution/events, then either:
- Use the CRM’s native field mapping if it lets you customize the JSON body (Close does, Copper does), or
- Pipe through Zapier / Make for the JSON shape transformation.
Generic Zapier / Make pattern
For any CRM that Zapier or Make supports as a trigger:
- Trigger: the relevant state change in your CRM (deal stage, lead status, etc.)
- Action: Webhooks → POST with the OpenPartner endpoint, key, and JSON shape from the payload section above
- Test: trigger the source event manually, watch the OpenPartner admin’s Review queue and partner’s commission ledger update
Identifying the user — the only hard part
The single thing that has to line up between your CRM and OpenPartner
is the userId. The cleanest patterns:
- Use email. Most CRMs have email on every contact. Make sure your
signup flow calls
identify({ userId: email })so OpenPartner stitches by the same value. - Use your auth system’s user ID and sync it to the CRM contact.
HubSpot custom field, Salesforce custom field, etc. Then the CRM
webhook reads the field and uses that as
userId. - Use external IDs. If your CRM and OpenPartner both reference your internal user/account ID via metadata, both webhooks (signup → identify and CRM stage → events) can use it consistently.
If userId doesn’t match what was stamped on the original click’s
identity, the event lands without an attribution and no commission accrues.
The Event row is still inserted (so you can debug), but no partner gets
paid. The fix is always at the identify step — make sure the same
identifier is used at signup and at CRM-event-fire time.
What’s not here yet
- Outbound to CRM — pushing partner attribution back into HubSpot /
Salesforce as a custom field on the contact. This is on the roadmap;
the current workaround is a separate Zapier job that reads
OpenPartner’s
/attributionsAPI and pushes to the CRM. - Native connectors — first-class HubSpot / Salesforce apps in their marketplaces. Coming. For now, the patterns above cover 95% of the use case at Zapier’s lower cost.
If you’re integrating a CRM not covered here and the pattern doesn’t fit cleanly, the issue tracker is open.