curl --request POST \
--url https://api.hacktionbase.com/v1/contacts \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"externalUserId": "user_123",
"anonymousId": "<string>",
"email": "jane@acme.com",
"phone": "+33612345678",
"firstName": "<string>",
"lastName": "<string>",
"source": "website",
"formId": "quote-form",
"formType": "quote",
"event": "quote.requested",
"traits": {},
"consent": {
"marketing": true,
"termsAccepted": true
}
}
'import requests
url = "https://api.hacktionbase.com/v1/contacts"
payload = {
"externalUserId": "user_123",
"anonymousId": "<string>",
"email": "jane@acme.com",
"phone": "+33612345678",
"firstName": "<string>",
"lastName": "<string>",
"source": "website",
"formId": "quote-form",
"formType": "quote",
"event": "quote.requested",
"traits": {},
"consent": {
"marketing": True,
"termsAccepted": True
}
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
externalUserId: 'user_123',
anonymousId: '<string>',
email: 'jane@acme.com',
phone: '+33612345678',
firstName: '<string>',
lastName: '<string>',
source: 'website',
formId: 'quote-form',
formType: 'quote',
event: 'quote.requested',
traits: {},
consent: {marketing: true, termsAccepted: true}
})
};
fetch('https://api.hacktionbase.com/v1/contacts', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.hacktionbase.com/v1/contacts",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'externalUserId' => 'user_123',
'anonymousId' => '<string>',
'email' => 'jane@acme.com',
'phone' => '+33612345678',
'firstName' => '<string>',
'lastName' => '<string>',
'source' => 'website',
'formId' => 'quote-form',
'formType' => 'quote',
'event' => 'quote.requested',
'traits' => [
],
'consent' => [
'marketing' => true,
'termsAccepted' => true
]
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.hacktionbase.com/v1/contacts"
payload := strings.NewReader("{\n \"externalUserId\": \"user_123\",\n \"anonymousId\": \"<string>\",\n \"email\": \"jane@acme.com\",\n \"phone\": \"+33612345678\",\n \"firstName\": \"<string>\",\n \"lastName\": \"<string>\",\n \"source\": \"website\",\n \"formId\": \"quote-form\",\n \"formType\": \"quote\",\n \"event\": \"quote.requested\",\n \"traits\": {},\n \"consent\": {\n \"marketing\": true,\n \"termsAccepted\": true\n }\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.hacktionbase.com/v1/contacts")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"externalUserId\": \"user_123\",\n \"anonymousId\": \"<string>\",\n \"email\": \"jane@acme.com\",\n \"phone\": \"+33612345678\",\n \"firstName\": \"<string>\",\n \"lastName\": \"<string>\",\n \"source\": \"website\",\n \"formId\": \"quote-form\",\n \"formType\": \"quote\",\n \"event\": \"quote.requested\",\n \"traits\": {},\n \"consent\": {\n \"marketing\": true,\n \"termsAccepted\": true\n }\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.hacktionbase.com/v1/contacts")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"externalUserId\": \"user_123\",\n \"anonymousId\": \"<string>\",\n \"email\": \"jane@acme.com\",\n \"phone\": \"+33612345678\",\n \"firstName\": \"<string>\",\n \"lastName\": \"<string>\",\n \"source\": \"website\",\n \"formId\": \"quote-form\",\n \"formType\": \"quote\",\n \"event\": \"quote.requested\",\n \"traits\": {},\n \"consent\": {\n \"marketing\": true,\n \"termsAccepted\": true\n }\n}"
response = http.request(request)
puts response.read_body{
"ok": true,
"requestId": "3c90c3cc-0d44-4b50-8888-8dd25736052a"
}Capture a Contact
Upsert a contact from a lead-source submission (contact form, quote request, newsletter signup…). Requires a public API key (hb_public_*). At least one identifier (externalUserId, email, phone, or anonymousId) is required. Matching walks externalUserId → email → phone → anonymousId; multiple matches create a conflicted contact instead of auto-merging. Processing is asynchronous — the submission is accepted and queued; deduplication and identity resolution happen downstream.
curl --request POST \
--url https://api.hacktionbase.com/v1/contacts \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"externalUserId": "user_123",
"anonymousId": "<string>",
"email": "jane@acme.com",
"phone": "+33612345678",
"firstName": "<string>",
"lastName": "<string>",
"source": "website",
"formId": "quote-form",
"formType": "quote",
"event": "quote.requested",
"traits": {},
"consent": {
"marketing": true,
"termsAccepted": true
}
}
'import requests
url = "https://api.hacktionbase.com/v1/contacts"
payload = {
"externalUserId": "user_123",
"anonymousId": "<string>",
"email": "jane@acme.com",
"phone": "+33612345678",
"firstName": "<string>",
"lastName": "<string>",
"source": "website",
"formId": "quote-form",
"formType": "quote",
"event": "quote.requested",
"traits": {},
"consent": {
"marketing": True,
"termsAccepted": True
}
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
externalUserId: 'user_123',
anonymousId: '<string>',
email: 'jane@acme.com',
phone: '+33612345678',
firstName: '<string>',
lastName: '<string>',
source: 'website',
formId: 'quote-form',
formType: 'quote',
event: 'quote.requested',
traits: {},
consent: {marketing: true, termsAccepted: true}
})
};
fetch('https://api.hacktionbase.com/v1/contacts', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.hacktionbase.com/v1/contacts",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'externalUserId' => 'user_123',
'anonymousId' => '<string>',
'email' => 'jane@acme.com',
'phone' => '+33612345678',
'firstName' => '<string>',
'lastName' => '<string>',
'source' => 'website',
'formId' => 'quote-form',
'formType' => 'quote',
'event' => 'quote.requested',
'traits' => [
],
'consent' => [
'marketing' => true,
'termsAccepted' => true
]
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.hacktionbase.com/v1/contacts"
payload := strings.NewReader("{\n \"externalUserId\": \"user_123\",\n \"anonymousId\": \"<string>\",\n \"email\": \"jane@acme.com\",\n \"phone\": \"+33612345678\",\n \"firstName\": \"<string>\",\n \"lastName\": \"<string>\",\n \"source\": \"website\",\n \"formId\": \"quote-form\",\n \"formType\": \"quote\",\n \"event\": \"quote.requested\",\n \"traits\": {},\n \"consent\": {\n \"marketing\": true,\n \"termsAccepted\": true\n }\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.hacktionbase.com/v1/contacts")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"externalUserId\": \"user_123\",\n \"anonymousId\": \"<string>\",\n \"email\": \"jane@acme.com\",\n \"phone\": \"+33612345678\",\n \"firstName\": \"<string>\",\n \"lastName\": \"<string>\",\n \"source\": \"website\",\n \"formId\": \"quote-form\",\n \"formType\": \"quote\",\n \"event\": \"quote.requested\",\n \"traits\": {},\n \"consent\": {\n \"marketing\": true,\n \"termsAccepted\": true\n }\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.hacktionbase.com/v1/contacts")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"externalUserId\": \"user_123\",\n \"anonymousId\": \"<string>\",\n \"email\": \"jane@acme.com\",\n \"phone\": \"+33612345678\",\n \"firstName\": \"<string>\",\n \"lastName\": \"<string>\",\n \"source\": \"website\",\n \"formId\": \"quote-form\",\n \"formType\": \"quote\",\n \"event\": \"quote.requested\",\n \"traits\": {},\n \"consent\": {\n \"marketing\": true,\n \"termsAccepted\": true\n }\n}"
response = http.request(request)
puts response.read_body{
"ok": true,
"requestId": "3c90c3cc-0d44-4b50-8888-8dd25736052a"
}Authorizations
Bearer authentication header of the form Bearer <token>, where <token> is your auth token.
Body
At least one of externalUserId, email, phone, or anonymousId is required.
Your business identifier for the person (strongest matching key).
"user_123"
SDK anonymous visitor id, if known.
Normalized (trim + lowercase) — the default dedup key.
"jane@acme.com"
Used for matching only when confidently parseable as E.164.
"+33612345678"
Where the submission came from.
"website"
"quote-form"
"quote"
Optional business event emitted alongside system events.
"quote.requested"
Arbitrary custom attributes (max 100 keys), merged per key.
Show child attributes
Show child attributes

