curl --request POST \
--url https://app.hookie.ai/v1/ingest/{ingest_key} \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '"<unknown>"'const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify('<unknown>')
};
fetch('https://app.hookie.ai/v1/ingest/{ingest_key}', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));import requests
url = "https://app.hookie.ai/v1/ingest/{ingest_key}"
payload = "<unknown>"
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text){
"submission_id": "<string>",
"idempotent": true
}{
"submission_id": "<string>",
"routed": [
"<string>"
],
"records": 123
}{
"error": "<string>",
"retry_after": 123,
"upgrade_url": "<string>"
}{
"error": "<string>",
"retry_after": 123,
"upgrade_url": "<string>"
}{
"error": "<string>",
"retry_after": 123,
"upgrade_url": "<string>"
}{
"error": "<string>",
"retry_after": 123,
"upgrade_url": "<string>"
}{
"error": "<string>",
"retry_after": 123,
"upgrade_url": "<string>"
}{
"error": "<string>",
"retry_after": 123,
"upgrade_url": "<string>"
}{
"error": "<string>",
"retry_after": 123,
"upgrade_url": "<string>"
}Ingest an event with an ingest key, routed by the project's mapping rules
curl --request POST \
--url https://app.hookie.ai/v1/ingest/{ingest_key} \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '"<unknown>"'const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify('<unknown>')
};
fetch('https://app.hookie.ai/v1/ingest/{ingest_key}', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));import requests
url = "https://app.hookie.ai/v1/ingest/{ingest_key}"
payload = "<unknown>"
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text){
"submission_id": "<string>",
"idempotent": true
}{
"submission_id": "<string>",
"routed": [
"<string>"
],
"records": 123
}{
"error": "<string>",
"retry_after": 123,
"upgrade_url": "<string>"
}{
"error": "<string>",
"retry_after": 123,
"upgrade_url": "<string>"
}{
"error": "<string>",
"retry_after": 123,
"upgrade_url": "<string>"
}{
"error": "<string>",
"retry_after": 123,
"upgrade_url": "<string>"
}{
"error": "<string>",
"retry_after": 123,
"upgrade_url": "<string>"
}{
"error": "<string>",
"retry_after": 123,
"upgrade_url": "<string>"
}{
"error": "<string>",
"retry_after": 123,
"upgrade_url": "<string>"
}Authorizations
An ik_live_… ingest key. Stored hashed; the plaintext is returned once at creation.
Headers
Optional. If a submission with this key already exists for the tenant, the request short-circuits with 200 before the monthly quota is charged.
Required only when the ingest key has require_signature set; ignored otherwise. Format: 't=,v1=' where v1 is HMAC-SHA-256 over '.' keyed with the key's signing secret.
"t=1700000000,v1=3ba8c0e9..."
Path Parameters
The plaintext ingest key. It is looked up by its first 16 characters (key_prefix) among non-revoked keys and then compared constant-time against the stored SHA-256 hash.
Body
Content-Type selects the parser: 'application/x-www-form-urlencoded' and 'multipart/form-data' are flattened into a shallow object (repeated fields become arrays; file parts become {filename, type, size} and their bytes are discarded). EVERY other Content-Type — including missing or wrong ones, which is deliberate so webhook providers that send JSON with an unhelpful Content-Type still work — is parsed as JSON. An empty body is accepted and becomes {}. Over 1,000,000 bytes yields 413.
Any JSON value. On this route the payload is fed to the project's mapping rules, so its shape is whatever the rules' conditions and mappings expect.
Response
Idempotent replay — a submission with this Idempotency-Key already exists for the tenant. No quota is charged when the duplicate is detected up front; a request that loses the unique-index race has already been counted against the monthly quota.