Endpoints
Webhooks
Subscribe a URL to workspace events. This is what the Make instant triggers call when a scenario is turned on and off.
So funktionieren Webhooks
Webhooks senden ein Event an deine URL, wenn im Workspace etwas passiert: ein neuer Kontakt, eine Buchung, eine bezahlte Bestellung, ein beendeter Anruf. Jede Zustellung ist signiert, damit du prüfen kannst, dass sie von Entagl kommt.
Einen Endpoint einrichten
Erstelle einen Endpoint mit POST /webhooks. Wähle Event-Typen aus der Liste unten (oder aus GET /events) oder nutze einen Platzhalter wie contact.* oder *. Die Antwort enthält das Signatur-secret genau einmal: Speichere es. Die Entagl-App für Make erledigt das für dich, wenn ein Szenario mit einem Entagl-Trigger eingeschaltet wird, und löscht den Endpoint, wenn es ausgeschaltet wird.
curl -X POST "https://api.entagl.com/api/v1/webhooks" \
-H "Authorization: Bearer ai_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"url": "https://example.com/entagl/webhooks",
"events": ["contact.created", "booking.*"],
"description": "CRM sync"
}'Der Webhook, den du auf der Seite Kanäle → Webhooks in der App einrichten kannst, verwendet das ursprüngliche Format: Der Body ist { event, workspace_id, occurred_at, data }, und X-Entagl-Signature ist sha256= gefolgt von einem HMAC-SHA256 des rohen Bodys. Alles Folgende beschreibt Endpoints, die über die API erstellt wurden.
Event-Format
Jede Zustellung ist ein POST mit einem JSON-Event. type sagt, was passiert ist, und data enthält die Details zu diesem Typ (siehe Event-Liste). actor sagt, wer es ausgelöst hat (ai, human, customer, system, api, import oder provider), und source.connection_id, welche Zugangsdaten, sodass eine Integration ihre eigenen Änderungen ignorieren kann. Update-Events enthalten außerdem changes mit den Namen der geänderten Felder.
{
"id": "evt_5c1f0a9e7b2d4e3f8a6c0b1d2e3f4a5b",
"object": "event",
"type": "contact.created",
"api_version": "2026-10-01",
"occurred_at": "2026-10-01T14:32:11.482Z",
"created_at": "2026-10-01T14:32:11.482Z",
"workspace_id": "00000000-0000-0000-0000-000000000000",
"actor": {
"type": "api",
"id": "user_9f2c1a"
},
"resource": {
"type": "contact",
"id": "6789"
},
"source": {
"connection_id": "apikey:42",
"via": "api"
},
"data": {
"contact_id": 6789,
"name": "Jane Doe",
"first_name": "Jane",
"last_name": "Doe",
"phone": "+15551234567",
"email": "jane@example.com",
"channel": "instagram",
"tags": [
"vip",
"botox"
],
"stage": {
"funnel_id": 3,
"stage_id": 14,
"stage_name": "Qualified"
},
"assigned_to_user_id": "user_9f2c1a",
"language": "en",
"custom_fields": {
"budget": "500-1000",
"preferred_branch": "Downtown"
},
"created_at": "2026-09-28T10:02:44.000Z",
"updated_at": "2026-10-01T14:32:11.482Z"
},
"changes": null
}- Content-Typeapplication/json
- User-AgentEntagl-Webhooks/2.0
- X-Entagl-SignatureSignatur, siehe unten.
- X-Entagl-Event-IdDie Event-ID. Nutze sie, um Duplikate zu überspringen.
- X-Entagl-Event-TypeDer Event-Typ, z. B. contact.created.
- X-Entagl-Delivery-Attempt1 beim ersten Versuch, dann 2, 3 …
Die Signatur prüfen
Der Header X-Entagl-Signature sieht so aus: t=1767225600,v1=5257a869…. t ist die Sendezeit in Unix-Sekunden. Jedes v1 ist ein HMAC-SHA256 des Textes {t}.{raw body} in Hex, erzeugt mit dem Secret deines Endpoints. Berechne ihn selbst und vergleiche in konstanter Zeit. Lehne die Anfrage ab, wenn t mehr als 5 Minuten von deiner Uhr abweicht. Nach einer Secret-Rotation gibt es 24 Stunden lang zwei v1-Werte: Akzeptiere die Anfrage, wenn einer der beiden passt.
import crypto from 'node:crypto';
import express from 'express';
const app = express();
// The secret returned once by POST /api/v1/webhooks (or by rotate-secret).
const SECRET = process.env.ENTAGL_WEBHOOK_SECRET;
// Read the RAW body: the signature covers the exact bytes Entagl sent.
app.post('/entagl/webhooks', express.raw({ type: 'application/json' }), (req, res) => {
const rawBody = req.body.toString('utf8');
if (!verifyEntaglSignature(req.get('X-Entagl-Signature') || '', rawBody, SECRET)) {
return res.status(400).send('invalid signature');
}
const event = JSON.parse(rawBody);
// Deliveries can repeat: skip an event.id you have already handled.
console.log(event.type, event.id);
res.sendStatus(200); // any 2xx within 10 seconds counts as delivered
});
function verifyEntaglSignature(header, rawBody, secret, toleranceSeconds = 300) {
const parts = header.split(',').map((p) => p.trim());
const t = Number(parts.find((p) => p.startsWith('t='))?.slice(2));
if (!Number.isFinite(t) || Math.abs(Date.now() / 1000 - t) > toleranceSeconds) return false;
const expected = crypto.createHmac('sha256', secret).update(`${t}.${rawBody}`).digest('hex');
return parts
.filter((p) => p.startsWith('v1='))
.some((p) => {
const got = p.slice(3);
return got.length === expected.length &&
crypto.timingSafeEqual(Buffer.from(got), Buffer.from(expected));
});
}
app.listen(3000);Zustellung und Wiederholungen
- Antworte innerhalb von 10 Sekunden mit einem beliebigen
2xx-Status. Weiterleitungen werden nicht verfolgt. - Alles andere ist ein fehlgeschlagener Versuch. Entagl versucht es nach 30 Sekunden, 2 Minuten, 10 Minuten, 1 Stunde, 6 Stunden und 24 Stunden erneut und markiert die Zustellung dann als fehlgeschlagen (insgesamt 7 Versuche).
410 Gonebeendet die Wiederholungen sofort und deaktiviert den Endpoint.- Ein Endpoint, der 72 Stunden lang immer wieder fehlschlägt, wird deaktiviert, und der Workspace-Owner wird benachrichtigt. Schalte ihn mit
PATCH /webhooks/:idund"status": "active"wieder ein. - Eine Zustellung kann mehrfach eintreffen, und Events können in falscher Reihenfolge eintreffen. Nutze die Event-ID, um Duplikate zu überspringen.
GET /webhooks/:id/deliverieszeigt die Versuche der letzten 30 Tage;POST …/redeliversendet einen erneut.
Datenschutz
Standardmäßig lassen Events weg, was Kunden geschrieben haben (Nachrichtentext, Transkripte, Zusammenfassungen, Notizen), und ihre Kontaktdaten (Namen, Telefonnummern, E-Mail-Adressen, Benutzernamen). Die Event-Liste zeigt diese Felder für jedes Event. Um sie zu erhalten, setze include_content und/oder include_contact_details am Endpoint. Nur ein Workspace-Owner oder Admin kann sie einschalten. In HIPAA-Workspaces werden sie nie gesendet, egal welche Einstellungen gelten. Wenn ein privates Feld weggelassen wird, nennt changes trotzdem seinen Namen, aber nie seinen alten Wert.
Event-Liste
Alle Event-Typen, die Entagl heute sendet. message.received und message.sent haben ein hohes Volumen: Ein Platzhalter schließt sie nie ein, nenne sie also beim Namen. GET /events liefert denselben Katalog als JSON.
Contacts (11)
contact.createdContact createdFires when a new contact is added: a first message from someone new, a manual add, an import or the API.
Felder der Kontaktdaten:namefirst_namelast_namephoneemailcustom_fields
contact.updatedContact updatedFires when any contact field or custom field changes. The envelope "changes" lists the changed fields and their previous values.
Felder der Kontaktdaten:namefirst_namelast_namephoneemailcustom_fields
contact.deletedContact deletedFires when a contact is deleted. Carries ids only.
contact.tag_addedContact tag addedFires when one or more tags are added to a contact.
contact.tag_removedContact tag removedFires when one or more tags are removed from a contact.
contact.stage_changedLifecycle stage changedFires when a contact moves to another funnel stage, or the stage is removed.
contact.assignedContact owner changedFires when a contact is assigned to a team member or unassigned.
contact.lead_capturedLead capturedFires when the AI (or a form) marks a contact as a lead, with a short summary of what they want.
Inhaltsfelder:summary
Felder der Kontaktdaten:namephoneemail
contact.mergedContacts mergedFires when two contacts are merged into one. The secondary contact id now redirects to the primary.
contact.opted_outContact opted outFires when a contact is marked do-not-contact or opts out of marketing messages.
note.createdContact note addedFires when a team member adds a note to a contact.
Inhaltsfelder:body
Conversations (7)
conversation.createdConversation openedFires when a new conversation starts on any channel.
conversation.reopenedConversation reopenedFires when a closed conversation is opened again, by a new customer message or a team member.
conversation.closedConversation closedFires when a conversation is closed by a team member, the API or auto-close, with the closing note if any.
Inhaltsfelder:closing_note_text
conversation.assignedConversation assignee changedFires when a conversation is assigned to a team member or unassigned.
conversation.ai_pausedAI paused on a conversationFires when AI replies are paused on a conversation (team member takeover, handover, spam, schedule or API).
conversation.ai_resumedAI resumed on a conversationFires when AI replies are switched back on for a conversation.
conversation.comment_createdConversation comment addedFires when a team member (or the API) adds an internal comment to a conversation. Customers never see comments.
Inhaltsfelder:body
Messages (3)
message.receivedIncoming messageFires for every message a customer sends, on any channel. High volume — subscribe only if you need every message.
Inhaltsfelder:text
message.sentOutgoing messageFires for every message sent to a customer: AI replies, team replies, campaigns and automations.
Inhaltsfelder:text
message.send_failedMessage failed to sendFires when a message to a customer could not be delivered by the channel.
Handovers (2)
handover.requestedHandover requestedFires when the AI hands a conversation to a human (customer asked for a person, complaint, or a question it could not answer).
Inhaltsfelder:summary
Felder der Kontaktdaten:customer_namecustomer_phone
handover.resolvedHandover resolvedFires when a team member (or auto-resolve) marks a handover as handled.
AI (3)
conversation.followup_sentFollow-up sentFires when the AI sends an automatic follow-up to a customer who went quiet.
ai_turn.failedAI reply failedFires when the AI could not produce a reply for a customer message (a fallback message may have been sent instead).
message_feedback.submittedAI reply ratedFires when a team member gives thumbs up or down to an AI reply.
Inhaltsfelder:comment
Bookings (6)
booking.createdBooking createdFires when an appointment is created for a customer.
Inhaltsfelder:conversation_summarydocuments
Felder der Kontaktdaten:customer_namecustomer_phone
booking.documents.addedBooking documents addedFires when a customer shares a document or photo in a conversation after their booking was created. Delivers only the new file(s).
Inhaltsfelder:documents
Felder der Kontaktdaten:customer_namecustomer_phone
booking.updatedBooking updatedFires when any booking detail changes (time, service, duration, location, notes). The envelope "changes" lists what changed.
Felder der Kontaktdaten:customer_namecustomer_phone
booking.rescheduledBooking rescheduledFires when a booking moves to a new date or time.
Felder der Kontaktdaten:customer_namecustomer_phone
booking.cancelledBooking cancelledFires when a booking is cancelled by the customer (through the AI), a team member, or the API.
Felder der Kontaktdaten:customer_namecustomer_phone
booking.status_changedBooking status changedFires when a booking status changes, e.g. confirmed, completed or no-show.
Felder der Kontaktdaten:customer_namecustomer_phone
Reminders (4)
reminder.createdReminder createdFires when a reminder is created.
Inhaltsfelder:titlenote
reminder.firedReminder dueFires when a reminder becomes due and the assignees are notified.
Inhaltsfelder:titlenote
reminder.closedReminder closedFires when a reminder is marked done.
Inhaltsfelder:titlenote
appointment_reminder.sentAppointment reminder sentFires when an automatic appointment reminder (WhatsApp template or SMS) is sent to a customer.
Orders (6)
order.createdOrder createdFires when an order is created by the AI, a team member, a store sync or the API.
Felder der Kontaktdaten:customer_namecustomer_phone
order.updatedOrder updatedFires when any order detail or item changes. The envelope "changes" lists what changed.
Felder der Kontaktdaten:customer_namecustomer_phone
order.status_changedOrder status changedFires when an order status changes (for example confirmed, shipped, delivered).
Felder der Kontaktdaten:customer_namecustomer_phone
order.paidOrder paidFires when an order is marked as paid.
Felder der Kontaktdaten:customer_namecustomer_phone
order.cancelledOrder cancelledFires when an order is cancelled.
Felder der Kontaktdaten:customer_namecustomer_phone
order.payment_proof_uploadedPayment proof uploadedFires when a customer (through the AI) or a team member attaches a payment receipt to an order.
Products (3)
product.createdProduct createdFires when a product is added to the catalog.
product.updatedProduct updatedFires when a product changes (price, stock, details).
product.deletedProduct deletedFires when a product is removed from the catalog.
Calls (3)
call.endedCall endedFires after every AI phone or WhatsApp call, with outcome and (opt-in) summary and transcript.
Inhaltsfelder:summarytranscript
Felder der Kontaktdaten:from_numberto_number
call.missedMissed callFires when an incoming call was not answered or an outgoing call got no answer.
Inhaltsfelder:summarytranscript
Felder der Kontaktdaten:from_numberto_number
call.voicemail_receivedVoicemail receivedFires when a caller leaves a voicemail.
Inhaltsfelder:transcript_text
Felder der Kontaktdaten:from_number
Campaigns (3)
campaign.completedCampaign finished sendingFires when a broadcast campaign finished sending to all recipients.
campaign_recipient.repliedCampaign replyFires when a recipient replies to a campaign message.
campaign_recipient.failedCampaign message failedFires when a campaign message could not be delivered to a recipient.
Forms (1)
form.submittedForm submittedFires when a customer submits a form (website widget, hosted link or in-chat intake).
Inhaltsfelder:fields
Account (3)
channel.disconnectedChannel disconnectedFires when a channel stops working (token expired, access removed) or is disconnected.
channel.reconnectedChannel healthy againFires when a previously failing channel works again.
credits.threshold_reachedCredits running lowFires when credit usage crosses 80%, 100% or 120% of the plan allowance.
Workspace (3)
tag.createdTag createdFires when a tag is added to the workspace tag list.
tag.updatedTag updatedFires when a workspace tag is renamed or recolored.
tag.deletedTag deletedFires when a workspace tag is deleted (it is removed from every contact).
Pfade sind relativ zur Basis-URL. Jede Karte zeigt die Berechtigung, die der Aufruf braucht, und jeden Parameter mit Typ und Grenzen.
/webhooksList webhook endpoints.
/webhooksCreate a webhook endpoint. The signing secret is returned once, in this response.
JSON-Body
- urlerforderlichstring (URL)
https:// URL that receives events, up to 2,048 characters. Private and internal addresses are refused (422 unsafe_url).
- eventserforderlicharray of strings
1–100 event types from GET /events, "<prefix>.*" (e.g. "contact.*") or "*". Wildcards never include message.received and message.sent; list those explicitly.
- descriptionoptionalstring
Up to 200 characters.
- filtersoptionalobject
Optional narrowing: channels (array of channel names, up to 20), actor_types (array, up to 7), sources (object of event type → source keys), ignore_own_changes (boolean — skip events caused by this same connection; on by default for connector-created endpoints).
- include_contentoptionalboolean
Include message text, transcripts and notes. Owner or admin only; refused on HIPAA workspaces.
- include_contact_detailsoptionalboolean
Include names, phones, emails and handles. Owner or admin only; refused on HIPAA workspaces.
- created_viaoptionalenum
Which tool created the endpoint.
Erlaubte Werte:
apimakezapiern8n
- A workspace can have up to 100 endpoints (409 endpoint_limit_reached).
/webhooks/:idRetrieve a webhook endpoint with its status and last delivery result.
Pfadparameter
- iderforderlichstring
Endpoint ID (starts with "we_").
/webhooks/:idUpdate an endpoint, or pause and re-activate it.
Pfadparameter
- iderforderlichstring
Endpoint ID.
JSON-Body
- urloptionalstring (URL)
New receiving URL.
- eventsoptionalarray of strings
New event list.
- descriptionoptionalstring | null
Up to 200 characters.
- filtersoptionalobject
Optional narrowing: channels (array of channel names, up to 20), actor_types (array, up to 7), sources (object of event type → source keys), ignore_own_changes (boolean — skip events caused by this same connection; on by default for connector-created endpoints).
- include_contentoptionalboolean
Owner or admin only to turn on.
- include_contact_detailsoptionalboolean
Owner or admin only to turn on.
- statusoptionalenum
Re-activating resets the failure count.
Erlaubte Werte:
activepaused
- On an endpoint that includes content or contact details, only an owner or admin can change url, events or filters.
/webhooks/:idDelete an endpoint (a Make trigger calls this when its scenario is turned off).
Pfadparameter
- iderforderlichstring
Endpoint ID.
/webhooks/:id/rotate-secretIssue a new signing secret. The previous secret keeps signing deliveries for 24 hours.
Pfadparameter
- iderforderlichstring
Endpoint ID.
/webhooks/:id/testSend a signed sample event to the endpoint now and return the result (delivered, status, error, duration_ms).
Pfadparameter
- iderforderlichstring
Endpoint ID.
JSON-Body
- eventoptionalstring
Event type to sample. Defaults to the endpoint's first non-wildcard event, else a generic "webhook.test" event.
/webhooks/:id/deliveriesRecent delivery attempts, newest first. Deliveries are kept for 30 days.
Pfadparameter
- iderforderlichstring
Endpoint ID.
/webhooks/:id/deliveries/:deliveryId/redeliverQueue a delivery to be sent again. Answers 202.
Pfadparameter
- iderforderlichstring
Endpoint ID.
- deliveryIderforderlichinteger
Delivery ID from the deliveries list.