Inbound webhooks
Send data into Kustomer from forms, servers and email with form hooks, web hooks and email hooks. Each hook gets a unique address and triggers workflows with the data you send.
An inbound hook is a unique address that accepts data and turns it into an event. Nothing happens to that data until a workflow with the hook as its trigger picks it up and decides what to create or update.
Diagram source
flowchart LR
F[HTML form] -- "POST, no auth" --> FH[Form hook]
S[Your server or a partner] -- "POST + Bearer key" --> WH[Web hook]
E[Order confirmation email] -- "Bcc" --> EH[Email hook]
FH & WH & EH --> EV{{"kustomer.hooks.<type>.<id>.receive"}}
EV --> WF[Workflow]
WF --> R[(Customers, conversations,<br/>custom objects…)]Create a hook
Create hooks in Settings › Platform › Inbound webhooks, or with the API:
post/v1/hooks/web
curl -X POST "https://acme.api.kustomerapp.com/v1/hooks/web" \
-H "Authorization: Bearer $KUSTOMER_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "title": "Shop orders", "description": "Order events from the storefront" }'{
"data": {
"type": "web_hook",
"id": "5825c452be3af71000ac6462",
"attributes": {
"description": "Order events from the storefront",
"eventName": "kustomer.hooks.web.5825c452be3af71000ac6462.receive",
"hash": "7733e0aab921040f15aa203d89ef8809a015c33d5303bd3642972d59602e43a6",
"url": "https://acme.api.kustomerapp.com/v1/hooks/web/7733e0aab921040f15aa203d89ef8809a015c33d5303bd3642972d59602e43a6",
"debug": false
}
}
}Use the returned url (or email, for email hooks) exactly; don't build it yourself. eventName is the workflow trigger. The equivalent endpoints for the other types are POST /v1/hooks/form and POST /v1/hooks/email.
Send data
curl -X POST "$HOOK_URL" \
-H "Authorization: Bearer $KUSTOMER_HOOK_KEY" \
-H "Content-Type: application/json" \
-d '{ "orderId": "4417", "email": "jane@example.com", "status": "shipped", "tracking": "1Z999AA10123456784" }'Create a dedicated API key with only the org.hooks role for each sender. It can post to hooks and nothing else, so a leaked key can't read your data. Send it in the Authorization header; a key in the query string returns 401.
<form action="https://acme.api.kustomerapp.com/v1/hooks/form/5f8481c6a22ab5b9d5f2dd8a/b6ffcac0…8e3a" method="POST" enctype="multipart/form-data">
<input name="name" required />
<input name="email" type="email" required />
<textarea name="message" required></textarea>
<input name="screenshot" type="file" accept="image/*" />
<button>Send</button>
</form>Prefer the /v1/hooks/form/{org}/{hash} form of the address over /v1/hooks/form/{hash}; it's the only one that supports reCAPTCHA.
Add the hook address as a Bcc on emails you already send, such as order confirmations. Put structured data in the HTML as JSON-LD:
<script type="application/ld+json">
{
"@context": "http://schema.org",
"@type": "Order",
"orderNumber": "4417",
"orderStatus": "http://schema.org/OrderInTransit",
"customer": { "@type": "Person", "email": "jane@example.com" }
}
</script>The workflow receives the parsed email (sender, recipients, subject, text and HTML bodies, headers and attachments) along with any JSON found in the markup.
Every hook answers with the ID of the transaction it created:
{ "data": { "id": "5825bf0fbe3af71000ac6427", "type": "transaction" } }Use the data in a workflow
Pick the hook as the workflow's trigger (Trigger app › Hooks in the builder, or its eventName in workflow JSON). The trigger's output is the transaction, and what you sent is under attributes.data:
{
"id": "5825cb4af5fefd1100f9c7c9",
"type": "transaction",
"attributes": {
"createdAt": "2026-10-21T15:20:42.554Z",
"data": {
"orderId": "4417",
"email": "jane@example.com",
"status": "shipped",
"tracking": "1Z999AA10123456784"
}
}
}So the fields you posted are referenced as /#steps.1.attributes.data.<field>:
{
"id": "find-customer",
"action": "kustomer.customer.find-by-email",
"params": { "email": "/#steps.1.attributes.data.email" },
"transitions": [{ "target": "upsert-order", "condition": { "op": "exists", "values": ["/#steps.find-customer.id"] } }]
}Files posted as form data become arrays of attachment descriptors (_id, name, contentType, contentLength) under the field name, ready to attach to a message or note.
Debug a hook
Turn on debugging to see exactly what Kustomer received:
curl -X PATCH "https://acme.api.kustomerapp.com/v1/hooks/web/$HOOK_ID" \
-H "Authorization: Bearer $KUSTOMER_API_KEY" -H "Content-Type: application/json" \
-d '{ "debug": true }'
curl "https://acme.api.kustomerapp.com/v1/hooks/web/$HOOK_ID/transactions" \
-H "Authorization: Bearer $KUSTOMER_API_KEY"Transactions received while debugging is on stay visible for 24 hours. The same view is in Settings › Platform › Inbound webhooks › View debug logs.
Hooks in app definitions
Apps can create hooks at install time, with their own event names, through the hooks section of an app definition. The app's workflows then use those event names as triggers.