Integrations / CRM sync

Sync contacts to your CRM

Keep HubSpot, Attio, Pipedrive, Salesforce or a spreadsheet in step with Crispy without polling. Crispy sends a webhook when a contact is created and when a contact field changes. Your Zapier, n8n, Make or Clay flow upserts the record on the other side.

The events

  • contact.created fires once for every new contact, whatever created it: for example an import, the REST API, an MCP tool, a connection sync, message sync, a profile viewer, or a send to someone not yet in your contacts. data.source names the path. Contacts written to your own database (bring-your-own storage) do not send it.
  • contact.updated fires when a write through Crispy changes a field the payload carries: name, headline, company, company domain, location, country, phone, email, LinkedIn URL, tags, custom attributes or external IDs. data.changed_fields lists the payload keys that changed. A write that changes nothing sends nothing.
  • One change is one event. A contact created and then filled in by the same call (for example upsert_lead or an import) sends one contact.created that already carries the filled-in fields, not a created event followed by an updated one.
  • Every payload carries the contact's current fields and its identifiers block, so you can upsert on identifiers.crispy_id or on an ID you imported into external_ids. Payloads never include message text.

What does not send contact.updated

Background refreshes keep Crispy's own copy current but do not send an update:

  • Profile details refreshed when you send to, or are viewed by, an existing contact.
  • Empty fields filled in when Crispy matches an existing contact during message sync, connection sync or a REST import (POST /api/v1/contacts/import). An MCP import_contacts merge does send it.
  • Connection status, campaign state and pipeline status. Use invitation.accepted, lead.added and the campaign events for those.
  • Merges. Subscribe to contact.merged to retire the losing record.

To reconcile anything a webhook did not carry, read the contact with GET /api/v1/contacts.

Setup

  1. In your automation tool, create a flow with a webhook trigger. Copy its URL.
  2. Subscribe Crispy to both events:
    curl -X POST https://crispy.sh/api/v1/webhooks \
      -H "Authorization: Bearer crispy_your_api_key" \
      -H "Content-Type: application/json" \
      -d '{
        "url": "https://hook.us2.make.com/your-make-webhook-id",
        "events": ["contact.created", "contact.updated"]
      }'
    The MCP tool create_webhook takes the same arguments.
  3. Verify the signature with the HMAC verification snippet and skip any Webhook-Event-Id you have already processed. Crispy retries failed deliveries with the same ID.
  4. Upsert the CRM record. Search by identifiers.external_ids (or email), update the match, otherwise create it and write the CRM ID back to Crispy with upsert_lead and external_ids. That write-back changes external_ids, so expect one contact.updated for it and ignore updates whose only changed field is external_ids if you do not need them.

Sample payloads

{
  "payload_version": "1",
  "id": "00000000-0000-0000-0000-000000000991",
  "event": "contact.created",
  "timestamp": "2026-10-13T09:15:02.000Z",
  "webhook_id": "00000000-0000-0000-0000-0000000009h3",
  "account_id": null,
  "data": {
    "identifiers": {
      "crispy_id": "contact_00000000000000000009c3",
      "linkedin_url": "https://www.linkedin.com/in/sam-rivera-example",
      "linkedin_provider_id": "ACoAAB9c3Sam",
      "member_id": null,
      "email": null,
      "external_ids": {}
    },
    "name": "Sam Rivera",
    "first_name": "Sam",
    "last_name": "Rivera",
    "headline": "Head of Growth at Example Co",
    "company_name": "Example Co",
    "connection_status": "connected",
    "tags": [],
    "custom_attrs": {},
    "contact_id": "00000000-0000-0000-0000-0000000009c3",
    "account_id": "00000000-0000-0000-0000-0000000009a1",
    "source": "connection_sync",
    "created_at": "2026-10-13T09:15:01.000Z"
  }
}
{
  "payload_version": "1",
  "id": "00000000-0000-0000-0000-000000000992",
  "event": "contact.updated",
  "timestamp": "2026-10-13T11:40:12.000Z",
  "webhook_id": "00000000-0000-0000-0000-0000000009h3",
  "account_id": null,
  "data": {
    "identifiers": {
      "crispy_id": "contact_00000000000000000009c3",
      "linkedin_url": "https://www.linkedin.com/in/sam-rivera-example",
      "linkedin_provider_id": "ACoAAB9c3Sam",
      "member_id": null,
      "email": "[email protected]",
      "external_ids": { "hubspot_contact_id": "hs-991" }
    },
    "name": "Sam Rivera",
    "headline": "VP Growth at Example Co",
    "company_name": "Example Co",
    "tags": ["q4-target"],
    "custom_attrs": {},
    "contact_id": "00000000-0000-0000-0000-0000000009c3",
    "account_id": "00000000-0000-0000-0000-0000000009a1",
    "source": "upsert_lead",
    "changed_fields": ["headline", "email", "tags"],
    "updated_at": "2026-10-13T11:40:11.000Z"
  }
}