How it worksFeaturesSolutionsGuides PricingFAQ Ask on WhatsApp
Help Developer reference

Connecting your own systems

How connectors work — the named REST endpoints Kooday pulls contacts from and pushes events to, how their credentials are stored and rotated, and how to test and trace a delivery.

Updated 16 August 2026 13 min read

A connector is one of your own HTTP endpoints, saved in Kooday under a name you choose, with its credential encrypted alongside it. Once saved, that name is how everything else in Kooday refers to your system: a campaign that pulls contacts from your CRM, an event that fans out to your automation platform, a tool your assistant calls mid-conversation, a booking action that confirms against your back office. One endpoint, saved once, credential rotated in one place.

You can have as many as you like. They are named, not typed — two connectors pointing at two different systems, or at the same system with different credentials, are perfectly normal. The only rule is that a name is unique within your account.

Two directions, and the direction decides everything. A read connector is one Kooday fetches from. A write connector is one Kooday sends to. Nearly every question about what a connector will and will not do is answered by which of the two it is.

Where to find it: open Integrations in your dashboard. Only team members with admin access can open it, and generic REST connectors need API access on your plan.

This page is the connector itself. For the catalogue of events you can send — what each one means, what its payload looks like, how to verify the signature — see the event reference.

The mental model

Read (GET)Write (POST)
The dashboard calls itRead (GET) — pull contacts inWrite (POST) — push events out
Who starts the callKooday, when a contact sync runsKooday, the moment an event fires
What Kooday sendsa GET with your static query parametersa POST carrying the event envelope
What Kooday does with the answermaps each record into a contact and saves itnothing — it only cares that you replied 2xx
Which config keys applyheaders, auth, query, mapping, paginationheaders, auth only

The consequence that surprises people most:

Kooday never calls your system while a customer is on the line to decide who they are. Contacts are synced in ahead of time and read locally. There is no live lookup against your CRM at the moment a call is placed.

There is one exception, and it is the one you would want: your assistant can be given a tool that fetches something from your system during a conversation — an order status, a booking slot — and that tool borrows a connector’s credentials by name. That is a deliberate, per-question call your assistant makes, not something the contact system does behind the scenes.

The five-minute version

To get Kooday events flowing into n8n, Make, Zapier or your own backend:

  1. IntegrationsNew integrationEvent webhook.
  2. Name it something you will recognise — n8n-prod.
  3. Endpoint URL: the webhook URL your automation gave you.
  4. Leave Auth secret and Connector config empty if your endpoint is open. Create integration.
  5. Press Test. You should see OK — … and a real request should have arrived at your endpoint.
  6. Scroll to Event routing and point the events you care about at the connector you created.

Deliveries start immediately. Before you rely on them, mint a signing secret and verify it — see the event reference.

Step 1 — Choosing a type

New integration opens a short catalogue: Choose a type of system to connect.

TileWhat it sets up
WhatsApp Cloud”Post-call WhatsApp notifications via Meta’s Cloud API.” A channel credential, not a REST connector — see connecting a channel
Contact source”Pull contacts from your CRM into Kooday on a GET.” A read connector
Event webhook”Push call & campaign events to your endpoint on a POST.” A write connector
Custom REST API”Any REST endpoint — set the direction, URL, auth, and config yourself.”

The tile is a starting point, not a category — the form is the same underneath. Pick the one closest to what you are building.

Step 2 — The form

FieldNotes
Name”A label unique to your account.” 1–120 characters. This is the handle everything else uses, so pick something durable — crm-contacts, not test2
DirectionRead (GET) — pull contacts in or Write (POST) — push events out. Fixed after creation
Endpoint URL”Absolute http(s) URL.” One fixed address — Kooday never appends a path or an id to it
Auth secret (optional)“Bearer token / API key / user:password. Encrypted; never shown again.”
Connector config (JSON)“Non-secret request shape.” See step 4

Direction cannot be changed later. Nor can the underlying capability tag Kooday derives from it. A connector created in the wrong direction has to be deleted and rebuilt — and if a campaign or an event route already names it, those need repointing. Get this one right on the first pass.

A duplicate name is refused with an integration named ‘X’ already exists.

Your endpoint must be reachable from the public internet over HTTPS. Kooday resolves and pins the address before connecting, and refuses private, loopback and cloud-metadata addresses outright — an endpoint on your office LAN or behind a VPN cannot be reached. Redirects are not followed either: a 301 to the canonical address is a hard failure, not a hop. Configure the final URL.

Step 3 — Credentials

The secret is encrypted before it is stored and is never returned — not to the dashboard, not to the API, not to us. After saving, the form shows only A secret ending …wxyz is stored. Leave blank to keep it. and the list shows the same last four characters in the Auth column.

There are four ways to present it, chosen in the config’s auth block:

auth.kindWhat Kooday sendsYour secret should be
bearerAuthorization: Bearer <secret>the token. This is the default when a secret exists
header<your header>: <secret> — also set "header": "X-Api-Key"the key
basicAuthorization: Basic <base64>user:password, literally, with the colon
noneno auth header(leave the secret empty)

To rotate, edit the connector and type the new secret. Leaving the box empty keeps the existing one; that is why it is a placeholder rather than a value. Typing whitespace is refused rather than silently blanking the credential: auth_secret must not be blank (omit it to keep the existing secret).

Rotation is instantaneous and there is no overlap window — the next request uses the new secret. Rotate in your system first, then here, so the gap is on your side and lasts seconds.

There is no OAuth flow and no refresh-token support. Kooday stores one static secret and sends it unchanged. That is the single biggest constraint on connecting a hosted CRM, and step 8 covers what it means in practice.

The signing secret is a different thing

The auth secret proves you to your system. The signing secret proves Kooday to your endpoint, so a write connector’s receiver can tell a real delivery from anyone who guessed the URL. Mint one per connector:

POST /api/integrations/{id}/signing-secret
→ { "signing_secret": "whsec_…", "signing_secret_hint": "…", "sig_version": 2 }

It is returned once. Store it like a password, and verify every delivery against it — the event reference has the exact computation and a worked example. Minting a replacement takes effect immediately, so deliveries signed with the old secret fail verification until they are retried.

Step 4 — Connector config

The Connector config (JSON) box is the non-secret request shape. The dashboard carries a How to configure panel next to it documenting every key with copyable examples; this is the summary.

KeyApplies toWhat it does
headersread + writeExtra request headers
authread + writeWhich scheme carries the secret (step 3)
queryread onlyStatic query parameters merged onto the URL
mappingread onlyHow a record in your response becomes a contact
paginationread onlyHow to walk to the next page

⚠️ query, mapping and pagination are silently ignored on a write connector. No error, no warning in the response — they have no effect. The help panel says so, and the panel is the only place that does: “This is a write connector — query, mapping and pagination are ignored; only headers and auth apply.”

Some header names are reserved and are dropped without comment: host, content-length, transfer-encoding, connection, and anything beginning x-kooday-. Kooday sets those itself.

Mapping a contact

mapping turns your response into contacts. phone is the only required field — a record Kooday cannot get a number from is not a contact it can use. Dot-paths walk nested JSON.

KeyMeaning
items_pathWhere the array lives — data.items
phoneRequired
name, email, external_idOptional
attributesA map of your own labels to dot-paths — {"city": "address.city"}

A worked read connector against a plain REST endpoint:

{
  "auth": { "kind": "bearer" },
  "query": { "status": "active" },
  "mapping": {
    "items_path": "data.items",
    "phone": "phone",
    "name": "full_name",
    "email": "email",
    "external_id": "id",
    "attributes": { "city": "address.city" }
  },
  "pagination": {
    "kind": "page", "page_param": "page", "size_param": "per_page",
    "size": 100, "start": 1
  }
}

pagination.kind is none, page or cursor. A cursor endpoint needs cursor_param and next_path — the dot-path where the next cursor appears in your response.

A sync walks at most 50 pages or 20,000 contacts, whichever comes first, and each page must be JSON, under 2 MB, and answered within 30 seconds.

Step 5 — Test

Test on the row runs the connector once, for real, and reports the shape of what came back: OK — array of 24, OK — object with keys: data, info. A failure is the actual reason — integration API error 401: …, integration unreachable, integration returned non-JSON response.

Use it the moment you save, and again after every credential rotation. It is the only place a wrong token surfaces immediately rather than as a red status hours later.

⚠️ Testing a write connector sends a real POST to your live endpoint, with the body {"test": true}. There is no sandbox mode and no confirmation prompt. Make sure your receiving flow either ignores that body or is safe to run — checking for the test key is the usual answer.

A read connector’s test fetches its first page and changes nothing.

Step 6 — Sending events to a connector

Event routing, below the list, is where a write connector starts receiving things. Each event gets a dropdown of your write connectors, defaulting to — none —.

“Send platform events to one of your webhook integrations. Messaging channels (WhatsApp, Instagram, Messenger) aren’t listed — they receive messages, not events; to message a customer after a call, use the bot’s post-call WhatsApp notification. Unmapped events don’t fan out.”

Two things follow:

  • An unrouted event goes nowhere, quietly. There is no error and no queued delivery — that is the intended behaviour, and it is also why a webhook that “isn’t firing” is usually a missing route rather than a broken endpoint.
  • Messaging connectors cannot be event targets. A WhatsApp or Instagram connector holds a channel credential; it is not an endpoint that can receive an envelope.

Several events can point at the same connector, and they will all verify with that connector’s one signing secret. That is deliberate — the secret belongs to the receiving endpoint, not to the subscription.

Only three events carry friendly names in the dropdown — Call completed, Per-contact outcome, Campaign finished. Everything else is listed by its real name, deal.won and the rest. The event reference is the list of what each one means and what it carries; the retry schedule, the envelope, the headers and the signature check all live there too, so they are not repeated here.

Step 7 — Watching deliveries

Two views, for two different questions.

Recent deliveries — press History on a row. One line per attempt, including every retry, newest first: when, which event, ok or failed, the HTTP status your endpoint returned, and the reason if it failed. This is where you look when something arrived late, twice, or not at all. Attempts are kept for 30 days.

Failed deliveries — a card at the bottom of the page: “Deliveries that exhausted their retries. Fix the integration, then replay to try again.” These have run out of attempts entirely. Fix the cause, then press Replay — a replay re-resolves the connector’s current URL and credentials, so a fix genuinely takes effect.

Broadcast deliveries are the exception and the button says so: “Broadcast deliveries can’t be replayed — a broadcast message is sent once or not at all.”

Neither view can leak your credentials: only status, attempt number and a display-safe reason are recorded. One caveat worth knowing if you write your own error responses — the first 200 characters of your endpoint’s error body are kept as the reason. If your endpoint echoes a token back in an error message, that fragment lands in the log.

When Kooday stops trying

A connector that fails repeatedly is paused rather than hammered: after five consecutive terminal failures, deliveries to it stop for an hour, doubling with each further trip up to a day. Other connectors are unaffected — a broken endpoint cannot slow down a working one.

A paused connector shows as error in the Status column with no separate badge. If you have fixed the cause and do not want to wait out the pause, clear it with POST /api/integrations/{id}/resume, then replay the failed deliveries.

Kooday can also tell you it happened: route the integration.unhealthy event to a different connector, and you get a delivery naming the paused connector, the failure count and when the pause lifts.

Step 8 — Where else a connector is used

Event routing is the most visible use, not the only one.

UseHow it names the connector
Contact sync for a campaignA read connector picked in the campaign form’s Integration dropdown
The default contact sourceThe first active read connector tagged as a contact source, when a campaign names none
A tool your assistant callsBy name, in the tool’s configuration — the tool borrows the connector’s URL host and credentials
Confirming an action against your systemBy name, on the action. Saving an action that names a connector you have not created is refused: no active connector named ‘X’ — create the integration first

If you have two read connectors both tagged as contact sources, which one a campaign uses is not defined. Name the connector explicitly on every campaign that has one, and the ambiguity goes away.

CRMs, and Zoho in particular

The connector speaks plain REST, so a CRM that exposes a contacts endpoint works — shape-wise, at least. The dashboard ships a Zoho example you can paste straight into the config box:

{
  "auth": { "kind": "header", "header": "Authorization" },
  "mapping": {
    "items_path": "data",
    "phone": "Phone",
    "name": "Full_Name",
    "email": "Email",
    "external_id": "id"
  },
  "pagination": {
    "kind": "cursor", "cursor_param": "page_token",
    "next_path": "info.next_page_token"
  }
}

Zoho’s envelope and its cursor pagination are handled properly, and the field names above are Zoho’s real ones. The problem is not the shape, it is the credential. The dashboard says it plainly:

“Zoho note: the secret is a Zoho access token, which expires ~hourly. Unattended sync needs OAuth refresh, which isn’t supported yet — this works for a manual “Sync now” with a fresh token.”

So a Zoho connector is a manual sync: paste a fresh token, run the sync, and expect 401 about an hour later. The same is true of Salesforce and HubSpot, and of any system that authenticates with expiring OAuth tokens. If you need unattended sync from one of those today, the working pattern is a small service of your own that holds the refresh token, exposes a stable endpoint with a static key, and lets Kooday read from that.

There is no Zoho integration in the product sense — no sign-in with Zoho, no field picker, no object mapping. There is a REST connector and a worked example of pointing it at Zoho.

What a connector cannot do

Being precise about this saves a lot of wasted configuration.

Supported
Fetch a contact list and save it into KoodayYes
Receive platform events at a fixed URL, signedYes
Lend its credentials to a tool your assistant callsYes
PUT, PATCH or DELETENo — GET and POST only
A URL that varies per record, e.g. /contacts/{id}No — the URL is fixed
A request body you compose, or template variables in itNo — a write connector sends the event envelope, unchanged
OAuth2, refresh tokens, expiring credentialsNo — one static secret
Reaching an endpoint on a private network or VPNNo
Following a redirectNo

And the one people ask about most:

⚠️ Writing a deal back into your CRM is not built. Nothing in Kooday updates a record in another system — not a deal, not a stage change, not a note, not a task. A write connector posts an event to your endpoint and stops there. If you want a deal to appear in your CRM, route deal.created and deal.stage_changed to your own automation and let it do the writing. That works today and is the supported pattern; it is your code making the write, not ours.

Limits

Connectors per accountNo limit; names must be unique
HTTP methodsGET, POST
Request timeout (sync, test)30 seconds
Response size (sync, test)2 MB, and it must be JSON
Event delivery timeout10 seconds — respond fast, work afterwards
Contact sync ceiling50 pages or 20,000 contacts per run
Delivery attempt history30 days
Pause after repeated failure5 consecutive failures → 1 hour, doubling to a maximum of 24 hours

Retries, backoff and duplicate semantics for event deliveries are in the event reference.

Troubleshooting

Test says “integration API error 401”

Your endpoint rejected the credential. In order: is auth.kind the scheme your system actually expects? For basic, is the secret stored as user:password with the colon? For header, did you set auth.header to the header name? And if this is a CRM, has the token expired — that is an hour, for Zoho.

Test says “integration redirected (301)”

Redirects are never followed. Put the final address in Endpoint URL — usually the https:// or www. form your server was redirecting to.

Test says “integration returned non-JSON response”

The response must be JSON even when the status is 200. An HTML error page, an empty body or a plain-text OK all fail here. For a write connector, return {"ok": true} rather than nothing.

Events aren’t reaching my endpoint

  1. Is there a row in Event routing for that event, pointing at your connector? An unrouted event produces nothing at all — no delivery, no error.
  2. Is the connector’s Direction write? Read connectors cannot be routed to.
  3. Press History. If attempts are listed and failing, the reason is in the Detail column. If nothing is listed, the event is not routed or has not fired.
  4. Check Failed deliveries — they may have exhausted their retries hours ago.

The status went red and nothing is arriving now

Repeated failures pause a connector for up to a day. Fix the cause, call POST /api/integrations/{id}/resume, then Replay the failed deliveries.

The status says “not used” but this connector was working

Any edit clears the health columns — a connector you renamed reads as not used until its next delivery, even if it was failing a minute earlier. Press Test to get a real answer immediately.

Contacts aren’t syncing

Check that the connector’s Direction is read, that the campaign’s Integration dropdown names it, and that mapping.phone points at a field that genuinely holds a phone number — a record without one cannot become a contact.

I removed a connector and deliveries are still failing

Deliveries already queued keep the address they were created with, and appear in Failed deliveries as Legacy webhook. They will exhaust their retries and stop. There is nothing to fix.

A sensible first configuration

Two connectors, which is what most accounts end up with:

Name        n8n-prod
Direction   Write (POST) — push events out
Endpoint    https://n8n.acme.com/webhook/kooday
Auth        (none — the URL is the secret, plus our signature)
Config      {}
Signing     minted, stored in n8n's credential vault

Event routing
  deal.created        → n8n-prod
  deal.stage_changed  → n8n-prod
  booking.created     → n8n-prod
  integration.unhealthy → n8n-prod

Name        crm-contacts
Direction   Read (GET) — pull contacts in
Endpoint    https://crm.acme.com/api/v1/contacts
Auth        bearer, a long-lived service token
Config      { "auth": { "kind": "bearer" },
              "mapping": { "items_path": "data", "phone": "mobile",
                           "name": "full_name", "external_id": "id" },
              "pagination": { "kind": "page", "page_param": "page",
                              "size_param": "limit", "size": 200, "start": 1 } }

One endpoint out, one endpoint in, and every downstream system — CRM, sheets, Slack, calendar — living in your automation platform rather than in Kooday. That is the shape to aim for: Kooday knows one address, and you change what happens next without touching Kooday at all.

Route integration.unhealthy from day one. It is the only thing that tells you a connector has gone quiet before somebody notices the data stopped.

Still stuck? We answer support mail the same working day.

Email support