HandshakeDeveloper docs

Handshake Integration API

Configure durable playbooks, send current customer events, and inspect each enrollment decision from your agency system.

Base URLhttps://api.handshakevoice.com/v1

API access is available to approved workspaces. Organization admins can create scoped keys in Settings → Integrations.

Authentication

Send a server-side API key as a bearer token. Keys belong to one organization, have explicit scopes, and are shown only once when created.

Authorization: Bearer hsk_live_...
Keep keys on your server. Do not use an integration key in browser or mobile client code.

Create a playbook draft

Start with a supported playbook type. Handshake owns the channel implementation and policy; clients do not submit prompts, disclosure language, caller ID, or provider settings.

RequestcURL
curl https://api.handshakevoice.com/v1/playbooks \
  --request POST \
  --header "Authorization: Bearer $HANDSHAKE_API_KEY" \
  --header "Content-Type: application/json" \
  --header "Idempotency-Key: renewal-september-2026" \
  --data '{
    "name": "Renewals due soon",
    "mode": "approval_required",
    "channel": "voice",
    "config": { "type": "renewal_outreach", "leadDays": 30 }
  }'
Response · selected fields201 Created
{
    "id": "4f260f43-3e89-4dc8-8cab-4f98ad51f154",
    "name": "Renewals due soon",
    "type": "renewal_outreach",
    "mode": "approval_required",
    "channel": "voice",
    "status": "draft",
    "config": { "type": "renewal_outreach", "leadDays": 30 },
    "version": 1
}

Idempotency: include an Idempotency-Key when creating a playbook, recording permission, or changing playbook state. Keys are valid for 24 hours and must be unique to the request body.

Endpoints

The v1 API is organized around durable playbooks and idempotent source events.

Playbooks

POST
/playbooks

Create a playbook draft.

playbooks:write
GET
/playbooks

List configured playbooks.

playbooks:read
GET
/playbooks/:id

Get one playbook.

playbooks:read
PATCH
/playbooks/:id

Update a draft or paused playbook.

playbooks:write
POST
/playbooks/:id/actions

Activate or pause a playbook.

playbooks:write
GET
/playbooks/:id/enrollments

List decisions and readiness.

playbooks:read

Events and permissions

POST
/events

Send current customer and subject state.

events:write
POST
/permissions

Record permission for an external customer ID.

permissions:write
GET
/routes

List available handoff routes.

playbooks:read

Webhooks

GET
/webhooks

List webhook endpoints.

webhooks:write
POST
/webhooks

Create a webhook endpoint.

webhooks:write
POST
/webhooks/:id/disable

Disable a webhook endpoint.

webhooks:write

Send source state

POST/events

Send the newest known state for one customer and one subject. Handshake evaluates every active playbook of the matching type. Reusing an event ID with different data or sending older state returns 409 conflict.

Request bodyapplication/json
{
  "source": "agency-management-system",
  "externalEventId": "policy-term-48291-v7",
  "occurredAt": "2026-09-01T14:20:00.000Z",
  "customer": {
      "externalId": "customer-48291",
      "firstName": "Maya",
      "lastName": "Chen",
      "phone": "+14155550123",
      "email": "maya@example.com",
      "state": "CA",
      "timeZone": "America/Los_Angeles",
      "routeId": "2f3ce41d-d58d-4582-839f-b459f6f77572"
  },
  "subject": {
    "type": "renewal_outreach",
    "policyTermId": "term-48291",
    "policyDescription": "Personal auto policy",
    "status": "active",
    "expiresAt": "2026-10-15T00:00:00.000Z",
    "replacementPolicyId": null
  }
}

Ingestion behavior

  • Atomic validation. A structurally invalid event is rejected without partial enrollment.
  • Stable identity. customer.externalId and the subject ID must come from the source system and remain stable.
  • No inferred routing. Omit routeId if no route is known; Handshake records a readiness blocker instead of guessing.
  • Permission is separate. An event may create a blocked enrollment when permission is missing; Handshake never invents permission.

Permission evidence

POST/permissions

Permission records attach to the same customer.externalId used in source events. Marketing permission requires written consent and an evidence URL. Informational permission accepts a customer-provided number or express consent.

Each record states whether it covers AI voice, real-time processing, and transcription. Handshake rejects incomplete or incompatible evidence rather than broadening its scope.

Errors

Errors use a stable code, a human-readable message, optional structured details, and a request ID. Handshake fails closed when source state, permission, routing, or a required provider is unavailable.

Example409 Conflict
{
  "error": {
    "code": "stale_source_event",
    "message": "The source event is not newer than the current subject state",
    "details": {
      "latestOccurredAt": "2026-09-01T14:20:00.000Z",
      "incomingOccurredAt": "2026-09-01T13:55:00.000Z"
    }
  },
  "requestId": "6fa2c6d9-..."
}
StatusCodeMeaning
400validation_failed

The request body or parameters are invalid.

400invalid_idempotency_key

The idempotency key is missing or malformed.

401invalid_api_key

The API key is missing, expired, revoked, or invalid.

403forbidden

The API key does not have the required scope.

404not_found

The requested resource does not exist in this organization.

409conflict

The resource changed or the requested transition is invalid.

409stale_source_event

The event is older than the current subject state.

422validation_failed

The event was rejected atomically.

503unavailable

A required dependency is unavailable. Retry the request.

Webhooks

Webhook endpoints receive selected lifecycle events over HTTPS. Delivery is at least once: acknowledge with any 2xx response and deduplicate by the event id.

playbook.activatedplaybook.pausedenrollment.createdenrollment.approvedenrollment.blockeddelivery.starteddelivery.failedcontact.suppressedcall.completedcall.failedhandoff.completed
Event bodyapplication/json
{
  "id": "evt_7e13d2ce-...",
  "type": "call.completed",
  "occurredAt": "2026-09-01T14:32:08.114Z",
  "data": {
    "entityType": "call",
    "entityId": "3a74670a-..."
  }
}

Verify signatures

Handshake signs the exact request body with HMAC-SHA256. Compute the signature over the following string and compare it with the value after v1, in webhook-signature:

webhook-id + "." + webhook-timestamp + "." + rawBody

Also reject stale webhook-timestamp values. Handshake does not follow redirects and will not deliver to private or reserved network addresses.