Skip to content

Slava Integration

Integration for connecting voice/text AI assistants with external services and CRM systems.

Overview

This integration provides webhook endpoints for the Happ voice/text assistant platform:

  1. Setup — Initialize database, secrets, and KV storage
  2. Init Webhook — Called BEFORE conversation starts (search contact in CRM by phone)
  3. Post Webhook — Called AFTER conversation ends (create/update records in CRM)
  4. Healthcheck — Monitor integration availability

Quick Start

bash
# Local development
pnpm dev

# Deploy to dev
pnpm deploy:dev

# Deploy to production
pnpm deploy

# View logs
wrangler tail --env dev

Project Structure

src/
├── index.ts              # Entry point: HTTP endpoints + handlers
├── types.ts              # TypeScript types and interfaces
├── handlers/
│   ├── setup.ts          # Database, secrets, KV initialization
│   ├── init-webhook.ts   # Called BEFORE conversation (CRM lookup)
│   ├── post-webhook.ts   # Called AFTER conversation (CRM update)
│   ├── healthcheck.ts    # Component health checks
│   └── init.ts           # Legacy init handler
├── migrations/
│   └── index.ts          # Database migrations
└── utils/
    ├── clients.ts        # Lazy-initialized clients (DB, Cache, Creds)
    └── logger.ts         # JSON logging utilities

API Endpoints

POST /setup

Initialize database tables, create empty secret placeholders, and set up KV storage.

Request:

json
{
  "force": false
}

Response:

json
{
  "success": true,
  "database": {
    "migrated": true,
    "tables": ["slava_calls"]
  },
  "secrets": {
    "created": ["API_KEY", "API_SECRET"],
    "existing": []
  },
  "errors": []
}

POST /init-webhook

Called by the voice/text assistant BEFORE starting a conversation. Use this to search for the contact in CRM by phone number and prepare context.

Request:

json
{
  "phone": "+380501234567",
  "conversationId": "conv_123"
}

Response:

json
{
  "firstMessage": "Hello John! How can I help you today?",
  "context": {
    "clientName": "John Doe",
    "company": "Acme Corp",
    "source": "Website",
    "previousInteractions": "Last call: 2024-01-10"
  }
}

POST /post-webhook

Called by the voice/text assistant AFTER the conversation ends. Use this to create or update records in CRM.

Request:

json
{
  "conversationId": "conv_123",
  "phone": "+380501234567",
  "transcript": "Full conversation transcript...",
  "outcome": "completed",
  "duration": 180
}

Response:

json
{
  "success": true,
  "message": "Conversation processed successfully",
  "recordId": "rec_456"
}

Outcome values: completed, transferred, no_answer, busy, failed


GET /health

Check availability of all integration components (D1, KV, Credentials).

Response:

json
{
  "timestamp": "2024-01-15T10:00:00.000Z",
  "testId": "test_1705312800000",
  "success": true,
  "checks": {
    "d1": { "success": true, "details": { "tableExists": true } },
    "kv": { "success": true, "details": { "writeRead": true } },
    "creds": { "success": true, "details": { "keysFound": ["API_KEY"] } }
  }
}

POST /webhook/:action

Generic webhook endpoint for custom actions. Handlers are called directly for processing.

Example:

bash
curl -X POST https://integ.happ.tools/slava/webhook/custom-action \
  -H "Content-Type: application/json" \
  -d '{"recordId": "123", "data": {...}}'

Response:

json
{
  "status": "accepted"
}

Environment Variables

Cloudflare Bindings (wrangler.toml)

BindingTypeDescription
INTEG_DBD1Database for credentials and data
INTEG_KVKVKV storage for caching

Global Secrets (Doppler)

VariableDescription
CRYPTO_KEYEncryption key for credentials
CRYPTO_SALTSalt for encryption

Integration Secrets (D1 creds table)

Configure your CRM-specific secrets in src/handlers/setup.ts:

typescript
const REQUIRED_SECRETS: (keyof ISlavaCredentials)[] = [
  "API_KEY",
  "API_SECRET",
  "WEBHOOK_URL",
];

Implementing CRM Integration

1. Define Credentials

Edit src/types.ts:

typescript
export interface ISlavaCredentials {
  API_KEY: string;
  API_SECRET: string;
  FOLDER_ID: string;
}

2. Update Setup Handler

Edit src/handlers/setup.ts:

typescript
const REQUIRED_SECRETS: (keyof ISlavaCredentials)[] = [
  "API_KEY",
  "API_SECRET",
  "FOLDER_ID",
];

3. Implement Init Webhook

Edit src/handlers/init-webhook.ts:

typescript
import { YourCRMClient } from "@happ-integ/your-crm";

export async function handleInitWebhook(payload: IInitWebhookPayload) {
  const secrets = await creds.get<ISlavaCredentials>("slava");
  const crmClient = new YourCRMClient(secrets.API_KEY);

  // Find contact by phone
  const contact = await crmClient.findByPhone(payload.phone);

  return {
    firstMessage: `Hello ${contact.name}! How can I help you?`,
    context: {
      clientName: contact.name,
      company: contact.company,
      // ...
    },
  };
}

4. Implement Post Webhook

Edit src/handlers/post-webhook.ts:

typescript
export async function handlePostWebhook(payload: IPostWebhookPayload) {
  const secrets = await creds.get<ISlavaCredentials>("slava");
  const crmClient = new YourCRMClient(secrets.API_KEY);

  // Update CRM record
  await crmClient.createCallRecord({
    phone: payload.phone,
    transcript: payload.transcript,
    outcome: payload.outcome,
    duration: payload.duration,
  });

  return { success: true };
}

Database Migrations

Create migrations in migrations/ folder:

migrations/0001_create_calls.up.sql:

sql
CREATE TABLE IF NOT EXISTS slava_calls (
  id TEXT PRIMARY KEY,
  phone TEXT NOT NULL,
  conversation_id TEXT UNIQUE,
  status TEXT NOT NULL DEFAULT 'initiated',
  transcript TEXT,
  outcome TEXT,
  duration INTEGER,
  created_at TEXT DEFAULT (datetime('now')),
  updated_at TEXT DEFAULT (datetime('now'))
);

CREATE INDEX idx_slava_calls_phone ON slava_calls(phone);
CREATE INDEX idx_slava_calls_status ON slava_calls(status);

Testing

bash
# Run tests
pnpm test

# Test healthcheck
curl https://integ.dev.happ.tools/slava/health

# Test init webhook
curl -X POST https://integ.dev.happ.tools/slava/init-webhook \
  -H "Content-Type: application/json" \
  -d '{"phone": "+380501234567", "conversationId": "test_123"}'

Troubleshooting

Credentials not found

  1. Run /setup endpoint first
  2. Fill in secrets via integ-admin → Secrets page
  3. Run /health to verify

Database table not found

  1. Ensure migrations are defined in src/migrations/index.ts
  2. Run /setup endpoint to apply migrations