Skip to main content

Overview

The Contact Capture API turns any form submission into a Contact — a first-class person identity that exists before (and independently of) a logged-in user account. Every submission is deduplicated, enriched, and emits events that flow into segments, automations, inbox, campaigns, analytics, and AI. Base URL: https://api.hacktionbase.com/v1 Both endpoints are asynchronous: the submission is validated at the edge, accepted with 202, and queued. Deduplication, identity resolution, and event emission happen downstream within seconds.

Authentication

Both endpoints require a public API key (hb_public_*), sent as a Bearer token:
Public keys are issued from Settings → API Keys in the dashboard (key type Public). They can be rotated and revoked there at any time.
Public keys are secret server-side credentials — never embed them in browser code. If your form is client-side, proxy the submission through your own backend (see Server-side proxy pattern below).

Upsert a contact

POST /contacts accepts a form submission and either creates a new contact or updates the matching one.

Request fields

At least one identifier (externalUserId, email, phone, or anonymousId) is required. Limits: string fields are capped at 2048 characters; traits is capped at 100 keys; keys containing $ or . are silently dropped. Provenance is captured automatically on every submission: ip, userAgent, and receivedAt are stored in the contact’s sources[] array along with source, formId, and formType.

Response

202 Accepted — the submission is queued for processing:
Downstream, the submission either creates a new contact or updates the matching one. When it matches two or more existing contacts, a new contact is still created — flagged identityStatus: "conflict" with the candidate ids in conflictsWith — and a contact.identity_conflict event is emitted. Conflicts are never auto-merged; resolve them from the dashboard (or by assigning the contact to a user).

Identify a contact

POST /contacts/identify resolves an existing contact and marks it identified — it never creates one. Use it when identity information arrives after the initial capture (e.g. the visitor signs up).
The response is the same 202 Accepted acknowledgment. Downstream:
  • Match found — the contact is enriched and marked identified (contact.identified emitted).
  • No match — the submission is dropped (identify never creates; a later re-submission will re-identify).
  • Ambiguous match — nothing is merged; a contact.identity_conflict event is emitted and the conflict surfaces in the dashboard.

Identity resolution

Matching walks the identifiers in priority order over all active contacts (leads, anonymous visitors, and users):
  1. externalUserId
  2. email (normalized)
  3. phone (E.164 only)
  4. anonymousId

Events

Every processed submission generates at least one event — there is no submission without an event. Events are written to the tenant event store during processing, then fanned out to the event bus (automations, segments, inbox, analytics, messaging, AI). System events: contact.created, contact.updated, contact.form_submitted, contact.identified, contact.merged, contact.identity_conflict. Business events: pass any name in the event field (e.g. quote.requested, newsletter.subscribed, demo.requested). Business events are dynamic — consolidate and map them per tenant in Analytics → Discovery with the Map Events modal.

Server-side proxy pattern

Keep the key on your server and forward form submissions from your own endpoint:
Pairing with the SDK: if the visitor is tracked by the Hacktionbase SDK, include its anonymous id in the submission (anonymousId) so the form contact links to the browsing session. The SDK exposes it via the session — the same id the widget uses for Hacktionbase.identify().

Error reference

Only edge validation errors are returned synchronously: Business outcomes are resolved downstream and never fail the request: a submission with no identifier, an identify with no match, an exhausted trial contact quota, or a frozen trial tenant is dropped (logged server-side). Ambiguous matches emit a contact.identity_conflict event and surface in the dashboard.

Operations note — kind backfill migration

Deployments that predate the Contact model must run the kind backfill on every tenant DB before shipping this feature (otherwise legacy users become invisible to kind-scoped reads):
The migration is idempotent and safe to re-run.