Skip to content

Datahash API

The Datahash API lets any backend send events to Signals directly. Use it when none of the packaged sources fit, or when you want full control over what is sent and when. Events sent through the API flow through the same pipeline as every other source: validation, normalization, hashing, deduplication, and delivery to your connected destinations.

The interface is REST over HTTPS. It accepts POST only, takes JSON, and returns JSON. There is no SDK, so any language that can make an HTTPS request works.

Post to https://api.datahash.com/v1/events with Content-Type: application/json.

Every request carries two headers. You receive both values when you set up Datahash API as a source in Studio.

HeaderValue
AuthorizationThe auth key for your instance. Send the key on its own, with no Bearer prefix.
datahash_cpidThe connection ID for the instance the events belong to.
Terminal window
curl -X POST https://api.datahash.com/v1/events \
-H "Authorization: $DATAHASH_AUTH_KEY" \
-H "datahash_cpid: $DATAHASH_CPID" \
-H "Content-Type: application/json" \
-d @event.json

The body carries a top level event_type and an events array, so one request sends one event or a batch. event_type takes one of two values, and it applies to every event in the request: send OFFLINE_CONVERSIONS for conversions that happened away from your site, and LEAD_CONVERSIONS for leads. Split a mixed set into one request per type.

Each entry in events has two blocks. event describes the conversion: general carries what happened, monetary carries what it was worth. user describes the person: general carries the platform identifiers that are never hashed, pii carries the hashed contact fields. Apply SHA-256 hashing to PII has snippets for producing those values in Python, Node, Java, PHP and SQL.

{
"event_type": "OFFLINE_CONVERSIONS",
"events": [
{
"event": {
"general": {
"event_name": "Purchase",
"event_time": 1684759609,
"action_source": "physical_store",
"event_source_url": "https://www.example.com/",
"event_id": "order-84315",
"transaction_id": "ABC123",
"content_category": "grocery",
"content_ids": ["ABC123", "XYZ789"],
"content_type": "product",
"content_name": "lettuce",
"delivery_category": "in_store",
"number_items": 2,
"contents": [
{
"id": "ABC123",
"quantity": 2,
"item_price": 5.99,
"delivery_category": "in_store"
}
]
},
"monetary": {
"currency": "USD",
"content_value": 11.98
}
},
"user": {
"general": {
"fb": {
"click_id_fb": "",
"lead_id_fb": "",
"fb_login_id_fb": ""
}
},
"pii": {
"email_1_hashed": "f476a1c3456adadd54b629f19cfc33711d2c96a7f6eb448c605ad7556c47c1b2",
"phone_1_hashed_format_b": "fff59dfb7f484898644f9982356cc882d2547a0a09ab82735684c663b70ecee4",
"first_name_hashed": "6ac7f00933165014d1db908e1181ffa75bc7298e8143aa61062b0a08ac6366c0",
"last_name_hashed": "d782450c381aff2aa3cddcf62cc9ed3e7e6db6f8c2658c0e4400da340dce3582",
"city_hashed": "1fff55ae51b6ec5231c5e1f4349d48b8e98c28db8f3eb05d1821cac20cdd82f8",
"country_hashed": "fb54e9062429a93785559529beda15c55f62c29be22267811c0e8346c14846d3",
"external_id": "cust-99213"
}
}
}
]
}
FieldRequiredFormatExample
event_nameRequiredString. The name the destination reports against.Purchase
event_timeRequiredInteger. UNIX timestamp.1684759609
action_sourceRequiredString. Where the conversion happened: physical_store, system_generated or other.physical_store
event_source_urlRequiredString. Absolute URL beginning http:// or https://, matching the verified domain.https://www.example.com/
event_idOptionalString. Your own ID for the event, used to deduplicate it against the same conversion arriving from another source.order-84315
transaction_idOptionalString. The transaction the conversion belongs to.ABC123
content_categoryOptionalStringgrocery
content_idsOptionalArray of strings[“ABC123”, “XYZ789”]
content_typeOptionalStringproduct
content_nameOptionalStringlettuce
delivery_categoryOptionalStringin_store
number_itemsOptionalInteger2
predicted_ltvOptionalString432.12
search_stringOptionalStringlettuce
statusOptionalStringregistered
contentsOptionalArray of objects. Each takes id, quantity, item_price and delivery_category.See the sample payload above.

action_source is passed through to the destination, so send the value that describes where the conversion happened rather than defaulting everything to other. See Event validation & schema for the rest of what a destination rejects.

FieldRequiredFormatExample
currencyRequiredString. ISO 4217 three letter code.USD
content_valueRequiredDouble42.54

Every field here ends in _hashed and takes a lowercase 64 character SHA-256 hash of the normalized value. Normalize first: a hash of an un-normalized value is a well formed hash that matches nothing, and neither Signals nor the destination can tell the difference. Identifier normalization & hashing has the per field rules.

Email, phone, name and country carry the most matching weight. Sending only geography, gender and date of birth produces an identifier combination Meta treats as invalid, so include email or phone on every event.

FieldWeightNormalize to
email_1_hashed, email_2_hashed, email_3_hashedImportantTrimmed and lowercased email address. Up to three per event.
phone_1_hashed_format_a, phone_1_hashed_format_b, and the same pair for phone_2 and phone_3ImportantDigits with the country code, no symbols, letters or leading zeros. Send E.164 in format_b. Up to three numbers per event.
first_name_hashed, last_name_hashedImportantLetters only, trimmed, lowercased, no punctuation, UTF-8.
city_hashedImportantLetters only, trimmed, lowercased, no punctuation, UTF-8.
state_hashedImportantIn the US, the two character ANSI abbreviation in lowercase. Outside the US, lowercase with no punctuation, special characters or whitespace.
country_hashedImportantLowercase two letter ISO 3166-1 alpha-2 code.
zipcode_hashedOptionalLowercase, no whitespace. In the US, the first five digits only. In the UK, Area/District/Sector format.
gender_hashedOptionalm or f.
date_of_birth_hashedOptionalDD/MM/YYYY.
doby_hashed, dobm_hashed, dobd_hashedOptionalDate of birth split into YYYY, 01 to 12, and 01 to 31.

Two fields in this block are sent as they are rather than hashed. Hashing them breaks them.

FieldFormat
mobile_ad_idThe device advertising ID, GAID or IDFA, unhashed.
external_idYour own ID for the person, unhashed.

user.general.fb carries Meta identifiers, all unhashed.

FieldWhat it carries
click_id_fbThe Meta click ID from the landing page URL.
lead_id_fbThe lead ID from a Meta lead ad. Keep it as text: a numeric field truncates long IDs, which is the most common cause of lead conversions never matching.
fb_login_id_fbThe Facebook login ID for the person.

A request returns one of two shapes. There are no error codes, only human readable strings in messages.

FieldWhat it carries
statusSuccess or Error.
messagesAn array naming any problem. Empty on success.
events_receivedThe number of events in the request.
{
"status": "Success",
"messages": [],
"events_received": 1
}
{
"status": "Error",
"messages": [
"One or more events are not valid. Entire batch will be rejected.",
"Missing required field event.general.action_source"
],
"events_received": 1
}

Validation is all or nothing per request. One invalid event rejects every event sent with it, and events_received counts what arrived rather than what was accepted, so a 1 there is not confirmation that anything was delivered. Read status on every response.

Two things follow from that. Keep batches small enough that a single bad record does not cost you a large upload, and fix the field named in messages before you resend rather than retrying the same body.