Integrations

Let a hosted agent read current data from your CRM, calendar, billing system, or internal application during a call.

An Integration lets a hosted agent ask your application for a current, bounded fact during a call. It is for information that changes: an appointment slot, order status, customer eligibility, or a current balance. Put stable scripts, policies, and FAQs in a Knowledge Base instead.

One endpoint, named actions

An Integration is one public HTTPS adapter endpoint and one encrypted, write-only signing secret. An Action is a named, typed read or write operation sent to that endpoint. Giving an Action to an agent is the allowlist: the agent cannot use any other operation merely because it shares the endpoint.

Native calendar booking uses provider account consent and confirmed booking tools, using the same caller-confirmation contract as write Actions.

The three parts

PartWhat it isWho controls it
IntegrationOne public HTTPS endpoint plus its signing secretYour workspace
ActionA named operation with input and output JSON schemasYour workspace
Agent accessThe explicit attachment that makes an Action available to one hosted agentYour workspace

For example, one https://app.example.com/skysay/tools Integration can have an lookup_customer Action and a get_available_times Action. Your adapter receives both at that URL and dispatches by tool.name; there is no one-URL-per-action requirement.

Set up a read Action

In the Workspace, open Integrations, choose Add integration, enter the endpoint and secret, then define the first Action. Creating the Integration and its first Action is atomic: an Integration is not saved without an allowed Action.

Before the first Integration exists, the page also lists the systems Skysay can reach, each marked Available now or Coming soon.

Both the first-run page and the page you see once you have Integrations end with Need another integration? Message us — which opens support chat, or your mail client when chat is unavailable. Naming the system you need is how it gets onto that list.

The REST equivalent is POST /v1/integrations. The generated API reference is authoritative for the current schema; this example shows the shape deliberately:

{
  "project_id": "proj_123",
  "name": "Customer system",
  "endpoint_url": "https://app.example.com/skysay/tools",
  "signing_secret": "a-random-secret-stored-only-by-you-and-skysay",
  "initial_action": {
    "name": "lookup_customer",
    "description": "Read the safe customer status for a confirmed caller.",
    "input_schema": {
      "type": "object",
      "properties": { "customer_id": { "type": "string" } },
      "required": ["customer_id"]
    },
    "output_schema": {
      "type": "object",
      "properties": { "status": { "type": "string" } },
      "required": ["status"]
    },
    "operation_kind": "read",
    "timeout_seconds": 5
  }
}

Add further Actions with POST /v1/integrations/{integration_id}/actions. Action names use lowercase letters, numbers, and underscores. Keep both schemas small and specific: the input schema is what the model may supply, and the output schema is the only shape Skysay accepts back from your adapter.

What endpoint validation means

At save time Skysay checks that the endpoint has a credential-free https URL with a hostname that resolves to a public IP address. It rejects loopback, private, link-local, multicast, embedded-user/password, and otherwise unsafe addresses. It does not send a health request, discover operations, or call your endpoint when you save the Integration.

That is why https://www fails: www is not a public DNS hostname. A complete URL such as https://app.example.com/skysay/tools can pass this check yet still fail at call time if it does not implement the signed JSON protocol below. Save-time validation protects the network boundary; it is not a live integration test.

Attach an Action to a hosted agent

In the Workspace, select an active hosted agent under Agent access, choose the Action, and save. Access is explicit and can be removed without deleting the Integration. Disabled Integrations are removed from the active worker allowlist.

With the API, update the hosted agent using PATCH /v1/agents/{agent_id} and include the selected Action in its tools array:

{
  "tools": [
    {
      "type": "connection",
      "connection_id": "conn_123",
      "tool_id": "itool_123",
      "name": "lookup_customer"
    }
  ]
}

The target agent and your bearer token determine the workspace; do not send an organization or project ID in this update body. tools replaces the agent's complete allowlist, so include any Actions or built-in tools you intend to keep.

Only the Action name, description, and input schema are available to the model. The endpoint URL and signing secret are never shown to the model.

What your adapter receives during a call

When the model decides an attached Action is useful, it supplies only the business arguments allowed by that Action's input schema. Skysay derives the rest from the canonical call and agent, then sends this JSON body to your Integration endpoint:

{
  "type": "skysay.integration_tool_call",
  "tool_call_id": "toolcall_123",
  "tool": {
    "id": "itool_123",
    "name": "lookup_customer",
    "operation_kind": "read"
  },
  "context": {
    "organization_id": "org_123",
    "project_id": "proj_123",
    "call_id": "call_123",
    "agent_id": "agent_123",
    "external_reference": "case_456",
    "external_group_reference": "batch_2026_08",
    "call_context": { "locale": "en-GB" }
  },
  "arguments": { "customer_id": "cust_789" }
}

organization_id, project_id, call_id, agent_id, external references, and call_context are injected by Skysay, not accepted from the model. Treat them as inputs to verify against your own records. In particular, do not put current balances, changing case history, or credentials in call_context.

Homepage demo sessions

A voice session started from the Skysay homepage demo is not a phone call. An agent shown there keeps the tools its owner configured. Webhook and Connection tools run as they do on a call, with two differences:

  • call_id and the external references are empty strings, and call_context is empty.
  • Actions run for the workspace that owns the homepage demo agents. Skysay sets that workspace, not the visitor or the model.
  • A homepage session can call tools at most 10 times, and one visitor at most 30 times a day. Ending the session is not counted.
  • When a tool fails, the agent is told only that it is unavailable. It never hears your endpoint's address or error text.

Built-in tools that need a phone caller (send_sms, transfer_to_human, request_callback, set_call_disposition) do not run in a homepage session. Nothing is sent or dialled. The agent is told that the conversation is in a browser, so it can explain that the action is not available there.

Verify the signature, then dispatch the Action

Skysay sends Content-Type: application/json plus these headers:

HeaderMeaning
X-Agent-Telco-TimestampUnix timestamp used for freshness verification
X-Agent-Telco-Signaturesha256=<hex HMAC-SHA256> of timestamp + "." + raw request body

Verify the HMAC over the exact raw request bytes before parsing or acting on the body. Reject stale timestamps (Skysay uses a five-minute tolerance), invalid signatures, and a context that does not belong to your organization/project/case. Then dispatch tool.name to your CRM, Calendly, CMS, billing system, or database. Your adapter—not the model—owns that mapping and its credentials.

Return one JSON object matching the Action's output schema. Skysay bounds the response to 64 KiB and rejects a non-object or schema-invalid response. The validated object is returned to the call; do not return a full customer record when a small safe projection will answer the question.

A timeout, non-2xx response, invalid JSON, oversized response, or schema mismatch is rejected as a failed tool result. Handle that condition honestly in the agent's instructions—ask the caller to try later or route to a human—rather than treating a failed lookup as an absent or zero value.

const expected = createHmac("sha256", process.env.SKYSAY_SIGNING_SECRET!)
  .update(`${timestamp}.${rawBody}`)
  .digest("hex");

if (!timingSafeEqual(`sha256=${expected}`, request.headers["x-agent-telco-signature"])) {
  return response.status(401).json({ error: "invalid signature" });
}

switch (body.tool.name) {
  case "lookup_customer":
    return response.json(await readSafeCustomerProjection(body.context, body.arguments));
  default:
    return response.status(400).json({ error: "unknown action" });
}

Read and write actions

Choose operation_kind: "read" or operation_kind: "write" explicitly when creating an Action. There is no default. Reads run freely; writes require a read-back and a later explicit caller yes under the shared business-action contract. This also applies to webhook tools and workflow Action steps. requires_confirmation is not a configurable bypass: every business write requires confirmation.

Your endpoint receives the durable action ID in tool_call_id. Deduplicate it, validate the server-injected ownership against your records, and enforce your own business rules. An uncertain result is not permission to retry a mutation under a fresh ID.

Production checklist

  • Keep the endpoint public HTTPS, but keep your CRM/API credentials only inside your adapter.
  • Verify the raw-body signature and freshness before every dispatch.
  • Authorize the injected organization, project, call, and external reference against your own records.
  • Return the smallest safe response that satisfies the Action output schema.
  • Use a separate read Action for each business capability and attach it only to agents that need it.
  • Use signed post-call events to reconcile outcomes; do not make a live Action response your only durable record.