Handshake Integration API
Configure durable playbooks, send current customer events, and inspect each enrollment decision from your agency system.
https://api.handshakevoice.com/v1API 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_...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.
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 }
}'{
"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
/playbooksCreate a playbook draft.
playbooks:write/playbooksList configured playbooks.
playbooks:read/playbooks/:idGet one playbook.
playbooks:read/playbooks/:idUpdate a draft or paused playbook.
playbooks:write/playbooks/:id/actionsActivate or pause a playbook.
playbooks:write/playbooks/:id/enrollmentsList decisions and readiness.
playbooks:readEvents and permissions
/eventsSend current customer and subject state.
events:write/permissionsRecord permission for an external customer ID.
permissions:write/routesList available handoff routes.
playbooks:readWebhooks
/webhooksList webhook endpoints.
webhooks:write/webhooksCreate a webhook endpoint.
webhooks:write/webhooks/:id/disableDisable a webhook endpoint.
webhooks:writeSend source state
/eventsSend 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.
{
"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.externalIdand the subject ID must come from the source system and remain stable. - No inferred routing. Omit
routeIdif 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
/permissionsPermission 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.
{
"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-..."
}validation_failedThe request body or parameters are invalid.
invalid_idempotency_keyThe idempotency key is missing or malformed.
invalid_api_keyThe API key is missing, expired, revoked, or invalid.
forbiddenThe API key does not have the required scope.
not_foundThe requested resource does not exist in this organization.
conflictThe resource changed or the requested transition is invalid.
stale_source_eventThe event is older than the current subject state.
validation_failedThe event was rejected atomically.
unavailableA 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{
"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 + "." + rawBodyAlso reject stale webhook-timestamp values. Handshake does not follow redirects and will not deliver to private or reserved network addresses.