This integration allows you to receive contacts from HiHello into any system using a webhook. This feature is available to all HiHello Enterprise subscribers. HiHello webhooks require an API endpoint. Below, you'll find more information about how the webhook works and a technical guide for setting it up in your HiHello account.
Introduction
One of the key values of HiHello is the ability to capture all leads and have them seamlessly show up in your company CRM. For popular platforms like Salesforce, HubSpot, and Microsoft Dynamics, HiHello provides native CRM integrations to streamline this process. If your organization utilizes a proprietary or less commonly used CRM, you can connect it to HiHello using a webhook so that any time a new contact is created, it is immediately sent over to your CRM.
What is a Webhook?
In the simplest terms, a webhook is a way for one app to automatically send information to another app the exact moment something happens. Webhooks are entirely driven by events. When a specific event occurs in the first app, it shoots a message (a webhook) over the internet to the second app signaling that an event just happened.
How does the HiHello Contacts Webhook work?
When a contact is created, updated, or deleted in HiHello, the system automatically sends a notification to the organization's configured webhook URL containing the contact's details — like tags, associated card info, lead capture data, and a timestamp — along with a signature the recipient can use to verify the message genuinely came from HiHello.
If your URL responds with a 200 OK to acknowledge receipt then the process ends there. If the delivery fails, the system will retry up to 5 times over the course of a few hours, and every attempt is logged for auditing. There's also a security check that blocks webhook URLs pointing to internal/private network addresses to prevent misuse.
The flowchart below shows the complete workflow for the HiHello Contacts Webhook:
Setting up the HiHello Contacts Webhook
The HiHello Contacts Webhook is for users with knowledge of APIs that need custom workflows created around contact changes in HiHello.
To setup the HiHello Contacts Webhook you must:
Be on the HiHello Enterprise plan.
Have an API endpoint (user URL in the diagram above) ready to receive events. HiHello will generate a signing secret. When HiHello sends a webhook, it uses the signing secret to create a fingerprint (HMAC-SHA256) of the message. The API endpoint can do the same math with the same secret and compare results to verify their authenticity. If they match, the message is genuine — not forged by a third party.
Set up the URL to the API endpoint that will receive the events in HiHello settings.
Potential use cases for the Contacts Webhook
Possibilities are endless and the user can decide what to do with the contact events received. Here are some possible use cases:
CRM sync — Automatically push new contacts into Salesforce, HubSpot, or a custom CRM when someone exchanges cards.
Lead routing — Trigger a sales workflow when a contact is created via a lead capture form at an event.
Email marketing — Add/remove contacts from Mailchimp or similar lists based on tags assigned in HiHello.
Slack/Teams notifications — Alert a sales channel when a high-value contact is added or tagged.
Analytics & reporting — Stream contact events into a data warehouse to track networking activity over time.
Contact deduplication — Detect and merge duplicates in an external system when a contact is created.
Access provisioning — Automatically grant portal or app access when a contact is added with a specific tag.
Follow-up automation — Trigger a personalized email or SMS sequence the moment a new contact is created.
Technical Guide
Overview
The HiHello Contacts Webhook allows your organization to receive real-time updates whenever contacts are created, updated, or deleted in HiHello. Instead of polling for changes, HiHello pushes contact data directly to your system the moment an event occurs.
This integration is ideal for organizations that:
- Use a custom CRM or internal system
- Need workflows triggered by contact activity
-
Want more flexibility than native integrations
How It Works
When a contact event occurs in HiHello, the following sequence takes place:
- A contact is created, updated, or deleted in HiHello.
- HiHello sends an HTTP POST request to your configured webhook endpoint.
- Your server receives and processes the JSON payload.
- Your server responds with HTTP 200 OK to confirm receipt.
If your endpoint does not return a success response, HiHello will retry delivery up to 5 times over several hours. All delivery attempts are logged for auditing purposes.
Webhook URLs pointing to internal or private network addresses are blocked for security.
Webhook Requirements
| Requirement | Details |
|---|---|
| HiHello Plan | Enterprise plan required |
| Endpoint Accessibility | Must be a publicly accessible HTTPS URL |
| Request Handling | Endpoint must accept HTTP POST requests and parse JSON |
| Response | Must return HTTP 200 OK promptly upon receipt |
| Security (Recommended) | Verify HMAC-SHA256 signatures on every incoming request |
Supported Events
| Event | Description |
|---|---|
| contact.created | Fires when a new contact is added to HiHello |
| contact.updated | Fires when an existing contact record is modified |
| contact.deleted | Fires when a contact is removed from HiHello |
Setup Guide
Step 1 — Create Your Endpoint
Set up a server endpoint that accepts POST requests, parses JSON payloads, and returns a 200 OK response immediately. Avoid doing heavy processing synchronously — offload to a queue if needed.
| Note: Return 200 OK immediately, then process the payload asynchronously. If your endpoint times out, HiHello will retry delivery. |
Example (Node.js / Express):
|
app.post('/hihello/webhook', async (req, res) => { const body = req.body; // Return 200 immediately, process asynchronously res.status(200).send('OK'); await processContactEvent(body); }); |
Step 2 — Configure Webhook in HiHello
In your HiHello account settings (UI image shown in previous section):
- Navigate to Settings > Integrations > Webhooks
- Enter your publicly accessible HTTPS endpoint URL
- Save the configuration
- Copy your signing secret — you will need this to verify requests
Step 3 — Verify Webhook Signatures (Recommended)
Each request from HiHello includes two headers used for signature verification:
| Header | Description |
|---|---|
| X-HiHello-Signature | HMAC-SHA256 signature of the payload |
| X-HiHello-Timestamp | Unix timestamp when the request was sent |
HiHello signs each payload using HMAC-SHA256 with your signing secret. Use constant-time comparison to prevent timing attacks. Reject requests with a timestamp older than 5 minutes.
Signature verification example (JavaScript):
|
const body = await req.text(); const signature = req.headers.get("X-HiHello-Signature"); const timestamp = req.headers.get("X-HiHello-Timestamp"); async function verifySignature(body, signature, timestamp, secret) { const encoder = new TextEncoder(); const key = await crypto.subtle.importKey( "raw", encoder.encode(secret), { name: "HMAC", hash: "SHA-256" }, false, ["sign"], ); const signed = await crypto.subtle.sign("HMAC", key, encoder.encode(`${timestamp}.${body}`)); const expected = Array.from(new Uint8Array(signed)) .map((b) => b.toString(16).padStart(2, "0")) .join(""); if (expected.length !== signature.length) return false; // Timing-safe comparison let mismatch = 0; for (let i = 0; i < expected.length; i++) { mismatch |= expected.charCodeAt(i) ^ signature.charCodeAt(i); } return mismatch === 0; } |
Simple Payload Example (JSON)
A minimal contact.created event — a contact with only a first name and an email address.
|
{ "event": "contact.created", "event_id": "2ea5ce11-caea-42ff-820c-8344b206bb23", "timestamp": "2026-05-01T14:30:00.000Z", "organization_id": "zT3TnjYsl2KlWM4KkETx", "version": "1", "data": { "contact_id": "lZks8pcFpkfNdqHEtOkK", "account_id": "eZjSP3MMrCLqq7tsQFEU", "associated_card_id": null, "card_owner_email": null, "source": "MANUAL", "live_contact": false, "added_at": 1746107400000, "updated_at": 1746107400000, "card_data": { "name": { "given": "Jane" }, "title": null, "company": null, "department": null, "headline": null, "fields": [ { "type": "email", "display": "jane@example.com" } ] }, "lead_capture_data": null, "notes": [], "tags": [] } } |
Full Payload Example
A contact.updated event with all fields populated — phones, emails, links, addresses, notes, dates, handles, tags, and lead capture data.
|
{ "event": "contact.updated", "event_id": "d04cdcc8-f6a8-4496-8327-5fb2644ad6c3", "timestamp": "2026-05-18T16:22:07.409Z", "organization_id": "zT3TnjYsl2KlWM4KkETx", "version": "1", "data": { "contact_id": "mROVDaXhlTwXLjWyfSWh", "account_id": "eZjSP3MMrCLqq7tsQFEU", "associated_card_id": "iniixof2Fcn9OSLNCSRA", "card_owner_email": "john.doe@company.com", "source": "MANUAL", "live_contact": false, "added_at": 1747584000000, "updated_at": 1747670400000, "card_data": { "name": { "given": "Jane", "middle": "Marie", "family": "Smith", "preferred": "Jamie", "prefix": "Dr.", "suffix": "Jr.", "accreditations": "PHD, MBA", "pronouns": "she/her" }, "title": "Head of Product", "company": "Acme Corp", "department": "Engineering", "headline": null, "fields": [ { "type": "phone", "display": "+1 415 555 1234", "e164": "+14155551234" }, { "type": "phone", "label": "cell", "display": "+1 415 555 5678", "e164": "+14155555678", "is_sms_only": false }, { "type": "phone", "label": "work,fax", "display": "+1 415 555 9999", "e164": "+14155559999" }, { "type": "email", "label": "work", "display": "jane.smith@acmecorp.com" }, { "type": "email", "label": "personal", "display": "jane@gmail.com" }, { "type": "address", "label": "work", "display": "123 Main St, San Francisco, CA, 94105" }, { "type": "url", "label": "linkedin", "display": "Jane Smith", "uri": "https://linkedin.com/in/janesmith", "widget": { "enabled": false } }, { "type": "url", "label": "website", "display": "My website", "uri": "https://www.janesmith.com", "widget": { "enabled": false } }, { "type": "url", "label": "twitter", "display": "@janesmith", "uri": "https://x.com/janesmith", "widget": { "enabled": false } }, { "type": "note", "display": "Met at SF Tech Summit. Interested in enterprise plan." }, { "type": "important_date", "label": "birthday", "display": "1990-03-15" }, { "type": "xbox", "display": "JaneGamer99" } ] }, "lead_capture_data": { "form_id": "5dcf1cae-c772-492a-b0d9-9b59894fcb7c", "form_name": "SF Tech Summit Sign-Up", "fields": [ { "label": "Company Size", "value": "50-200" }, { "label": "Interest", "value": "Enterprise Plan" }, { "label": "Preferred Contact Method", "value": "Email" } ] }, "notes": [ { "id": "6edfc9e3-e5fe-4768-a347-dc6274a316ca", "text": "Follow up about enterprise pricing next week", "added_at": 1747584100000, "updated_at": 1747584100000 } ], "tags": [ { "id": "zIQgteDIbU8oyJR0ih6Y", "name": "SF Tech Summit 2026" }, { "id": "ru7r5U1YeWMY97bQhZXC", "name": "Enterprise Lead" } ] } } |
Contact Deleted Behavior and Example
When a contact is deleted, HiHello emits a contact.deleted webhook event.
The delete payload intentionally removes all mutable contact profile data while preserving minimal identifiers required for downstream systems to reconcile the deletion.
Preserved fields
The following fields remain populated after deletion:
- contact_id
- account_id
- organization_id
- card_owner_email
- live_contact
- notes (empty array)
- tags (empty array)
- Event metadata (event, event_id, timestamp, version)
Cleared fields
The following fields are set to null after deletion:
- associated_card_id
- source
- added_at
- updated_at
- card_data
- lead_capture_data
Default values
The following default values are currently observed in delete payloads:
- live_contact: false
- notes: []
- tags: []
Integration guidance
Integrations should use contact_id as the canonical identifier for mapping HiHello contacts to external systems.
Because contact profile data is removed from the delete payload, downstream systems should not rely on fields such as:
- phone number
- name
- associated card ID
Instead, integrations should maintain a persistent mapping between:
- contact_id
- external system record ID
This allows downstream systems to correctly archive, delete, or deactivate the corresponding external record when a contact.deleted event is received.
Example:
|
"data": { "contact_id": "cnt_123456789", "account_id": "acc_987654321", "associated_card_id": null, "card_owner_email": "owner@example.com", "source": null, "live_contact": false, "added_at": null, "updated_at": null, "card_data": null, "lead_capture_data": null, "notes": [], "tags": [] }, "event": "contact.deleted", "event_id": "evt_abc123xyz789", "organization_id": "org_456789123", "timestamp": "2026-05-27T20:07:19.281Z", "version": "1" } |
Payload Reference
Every webhook POST body is a JSON object. The following tables document every field, its type, presence behavior, and expected values.
Presence legend:
| always present | Field is always included in the payload |
| nullable | Always present in the payload, but may be null |
| empty array | Always present, but may be an empty array |
| omitted if not applicable | Field is absent from the payload entirely when not set (not null) |
Top-Level Envelope
| Field | Type | Presence | Description & Expected Values |
|---|---|---|---|
| event | string enum | always present | Event type. One of: contact.created contact.updated contact.deleted |
| event_id | string (UUID) | always present | Unique identifier for this event delivery. Use for deduplication. |
| timestamp | string (ISO 8601) | always present | UTC datetime when the event was emitted. e.g. 2026-05-18T16:22:07.409Z |
| organization_id | string | always present | Unique ID of the organization that owns the contact. |
| version | string | always present | Schema version. Currently always 1. |
| data | object | always present | Contact payload. See data object below. |
data Object
| Field | Type | Presence | Description & Expected Values |
|---|---|---|---|
| contact_id | string | always present | Unique ID of the contact record. |
| account_id | string | always present | ID of the HiHello account this contact belongs to. |
| associated_card_id | string | null | nullable | ID of the digital business card associated with this contact. null if no card is associated. |
| card_owner_email | string | null | nullable | Email of the HiHello user who owns the associated card. null if no card is associated or the card owner has no email on file. |
| source | string enum | always present (nullable for delete) | How the contact was added. One of: “MANUAL”, “SCAN”, “SHARE_TO_EXCHANGE”, “EXTERNAL_ADDRESS_BOOK”, “NEARBY” (deprecated — legacy Bluetooth) |
| live_contact | boolean | always present | true if added via another HiHello user sharing their card. false for all other sources. |
| added_at | integer (ms) | always present | Unix timestamp (ms) when the contact record was first created. Not to be confused with added_at on individual notes. |
| updated_at | integer (ms) | always present | Unix timestamp (ms) when the contact record was last updated. Not to be confused with updated_at on individual notes. |
| card_data | object | always present | Card fields. See card_data below. |
| lead_capture_data | object | null | nullable | Form data collected via a lead capture form. null if no lead capture form was used. See lead_capture_data below. |
| notes | array<object> | empty array | Private notes added by the HiHello user after receiving the contact. See notes[] below. |
| tags | array<object> | empty array | Tags applied to this contact. Each item has id (string) and name (string). Tag names may contain arbitrary text including markdown and auto-generated consent language. |
data.card_data Object
| Field | Type | Presence | Description & Expected Values |
|---|---|---|---|
| name | object | always present | Always present. At minimum given is populated. Full name is parsed from a single input into components. See name below. |
| title | string | null | nullable | Job title. null if not set. |
| company | string | null | nullable | Company name. null if not set. |
| department | string | null | nullable | Department name. null if not set. |
| headline | string | null | nullable | Short bio/tagline from the card owner's live profile. May be present when live_contact is true. null if not set. May contain trailing whitespace or newline characters. |
| fields | array<object> | empty array | All contact info fields in display order. No field type is guaranteed to be present — the array may be empty. Order is not grouped by type. Each item has a type discriminator. See fields[] below. |
data.card_data.name Object
| Field | Type | Presence | Description & Expected Values |
|---|---|---|---|
| given | string | always present | First / given name. The only required contact field. |
| middle | string | omitted if not applicable | Middle name. Parsed from full name input. |
| family | string | omitted if not applicable | Last / family name. Parsed from full name input. |
| preferred | string | omitted if not applicable | Preferred name / nickname. |
| prefix | string | omitted if not applicable | Name prefix. e.g. Dr., Mr. |
| suffix | string | omitted if not applicable | Name suffix. e.g. Jr., III |
| maiden | string | omitted if not applicable | Maiden name. |
| accreditations | string | omitted if not applicable | Credentials / degrees. e.g. PHD, MBA |
| pronouns | string | omitted if not applicable | Preferred pronouns. e.g. she/her, they/them |
data.card_data.fields[] — By Type
| Note: The fields array contains zero or more entries in display order. No field type is guaranteed to be present and order is not grouped by type. Within each entry, fields marked "omitted if not applicable" are absent rather than null. Null on delete actions |
type: "phone"
| Field | Type | Presence | Description & Expected Values |
|---|---|---|---|
| type | string | always present | Always "phone". |
| display | string | always present | Human-readable formatted number, may include extension. e.g. +1 415 555 1234 ext. 2 |
| e164 | string (E.164) | always present | Normalized number without extension. e.g. +14155551234 |
| label | string enum | omitted if not applicable | Phone type. One of: “cell” (Mobile in UI), “direct”, “home”, “main”, “office”, “work”, “work,fax” (Fax in UI) |
| extension | string | omitted if not applicable | Phone extension. Only present when the number has an extension. |
| is_sms_only | boolean | omitted if not applicable | Only present when label is cell (Mobile). Can only be true for live contacts — always false for non-live contacts. |
type: "email"
| Field | Type | Presence | Description & Expected Values |
|---|---|---|---|
| type | string | always present | Always "email". |
| display | string | always present | Email address. |
| label | string enum | omitted if not applicable | Email type. One of: personal, work |
type: "url"
| Field | Type | Presence | Description & Expected Values |
|---|---|---|---|
| type | string | always present | Always "url". |
| label | string enum | always present | Platform or link type. Always present. One of: website, link, linkedin, instagram, facebook, twitter, snapchat, tiktok, xing, pinterest, whatsapp, telegram, discord, line, zoom, microsoftteams, googlemeet, webex, venmo, paypal, cashapp, youtube, vimeo, twitch, brightcove, spotify, applemusic, soundcloud, behance, dribbble, github, calendly, microsoftbookings, patreon, yelp, file |
| display | string | always present | Human-readable display label. When no custom label is set, the raw URL may be used as the display value. |
| uri | string (URL) | always present | Full URL. For file labels, may be a gs:// Google Storage URI. |
| widget | object | always present | Controls whether the URL renders as an embedded preview on the card |
type: "widget"
| Field | Type | Presence | Description & Expected Values |
|---|---|---|---|
| enabled | boolean | always present | True for video URLs (YouTube, Vimeo, Brightcove) with “feature video?” turned on. False for all plain links. |
type: "address"
| Field | Type | Presence | Description & Expected Values |
|---|---|---|---|
| type | string | always present | Always "address". |
| display | string | always present | Full address as a single formatted string. |
| label | string enum | omitted if not applicable | Address type. One of: work, mailing, home |
type: "note"
| Field | Type | Presence | Description & Expected Values |
|---|---|---|---|
| type | string | always present | Always "note". |
| display | string | always present | Free-text written by the card owner, or added by the HiHello user on a manual contact. Shown as a field on the card itself. Multiple note entries may appear. Not to be confused with data.notes which are private notes added by the HiHello user. |
type: "important_date"
| Field | Type | Presence | Description & Expected Values |
|---|---|---|---|
| type | string | always present | Always "important_date". |
| display | string (ISO 8601) | always present | Date in YYYY-MM-DD format. |
| label | string enum | always present | Date type. One of: birthday, anniversary |
type: "xbox" | "playstation" | "nintendo" | "wechat" | "signal" | "skype" | "zelle"
| Note: Unlike other field types, these use type as the platform identifier directly rather than as a structural discriminator. There is no separate label field. This is a known inconsistency. |
| Field | Type | Presence | Description & Expected Values |
|---|---|---|---|
| type | string enum | always present | Platform identifier. One of: xbox, playstation, nintendo, wechat, signal, skype, zelle |
| display | string | always present | Username, handle, or account identifier. No URL. e.g. Nintendo Switch friend code SW-1234-5678-9012 |
data.lead_capture_data Object
Only present when the contact was captured via contact exchange with a lead capture form. null otherwise.
The organization configures the lead capture form and its custom fields. card_data contains what the responder fills out about themselves; lead_capture_data.fields contains their responses to the organization's custom questions.
| Field | Type | Presence | Description & Expected Values |
|---|---|---|---|
| form_id | string (UUID) | always present | Unique identifier of the lead capture form used. |
| form_name | string | always present | Human-readable name of the form as configured by the organization. |
| fields | array<object> | always present | Responses to the form's custom questions. Each item has label (string — the question text) and value (string — the response). Multi-select responses are comma-separated within a single string value. |
data.lead_capture_data.fields[] Object
Each entry represents a response to one custom question on the lead capture form.
| Field | Type | Presence | Description & Expected Values |
|---|---|---|---|
| label | string | always present | The form field label / question text as configured by the organization. |
| type | string enum | always present | Field type. One of: text dropdown multi_select |
| value | string | array<string> | always present | The responder's answer. A plain string for text and dropdown fields. An array of strings for multi_select fields. |
data.notes[] Object
Private notes added by the HiHello user after receiving the contact — e.g. personal observations or follow-up reminders. Not to be confused with type: "note" entries in card_data.fields, which are notes written on the card itself.
| Field | Type | Presence | Description & Expected Values |
|---|---|---|---|
| id | string (UUID) | always present | Unique identifier for the note. |
| text | string | always present | Note content. |
| added_at | integer (ms) | always present (nullable for delete) | Unix timestamp (ms) when the note was created. |
| updated_at | integer (ms) | always present (nullable for delete) | Unix timestamp (ms) of the most recent edit. |
data.tags[] Object
Tags applied to the contact. May be empty. Tag names may contain arbitrary text including markdown and auto-generated consent language from lead capture flows.
| Field | Type | Presence | Description & Expected Values |
|---|---|---|---|
| id | string | always present | Unique identifier for the tag. |
| name | string | always present | Tag label. May contain arbitrary text, markdown formatting, or auto-generated consent language. e.g. "Summer Conference 2026" or "Consented to [terms](https://example.com)" |
| is_consent | boolean | always present | true if the tag was auto-generated from a consent agreement (e.g. lead capture terms acceptance). false for regular org or user-created tags. |
Accessing Fields in Code
|
// Get first email address // Get all URLs // Get LinkedIn specifically // Get lead capture form answers // Get tags // Get contact owner email // Check if contact is associated with a HiHello card owner // Check if live contact |
Security Best Practices
- Always verify webhook signatures before processing any payload
- Reject requests with a timestamp older than 5 minutes
- Use HTTPS exclusively — plain HTTP endpoints will not be accepted
- Store your signing secret securely (e.g. environment variable or secrets manager)
- Return 200 OK immediately and process asynchronously
-
Use event_id to deduplicate — HiHello may retry if your first response was delayed
Troubleshooting
| Symptom | Resolution |
|---|---|
| No events received | Confirm your endpoint URL is publicly accessible over HTTPS |
| HTTP errors in logs | Ensure your server returns HTTP 200 OK — any other status triggers a retry |
| Signature mismatch | Check that you are using the correct signing secret and computing the HMAC correctly |
| Duplicate events | Use event_id to deduplicate — HiHello may retry if your first response was delayed |
| Endpoint times out | Return 200 OK immediately and process the payload asynchronously |
Need Help?
Contact your HiHello Enterprise Account Manager for assistance with setup, debugging, or advanced configuration. Have your organization ID and endpoint URL ready for fastest resolution.