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:
- Setup — Initialize database, secrets, and KV storage
- Init Webhook — Called BEFORE conversation starts (search contact in CRM by phone)
- Post Webhook — Called AFTER conversation ends (create/update records in CRM)
- Healthcheck — Monitor integration availability
Quick Start
# Local development
pnpm dev
# Deploy to dev
pnpm deploy:dev
# Deploy to production
pnpm deploy
# View logs
wrangler tail --env devProject 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 utilitiesAPI Endpoints
POST /setup
Initialize database tables, create empty secret placeholders, and set up KV storage.
Request:
{
"force": false
}Response:
{
"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:
{
"phone": "+380501234567",
"conversationId": "conv_123"
}Response:
{
"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:
{
"conversationId": "conv_123",
"phone": "+380501234567",
"transcript": "Full conversation transcript...",
"outcome": "completed",
"duration": 180
}Response:
{
"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:
{
"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:
curl -X POST https://integ.happ.tools/slava/webhook/custom-action \
-H "Content-Type: application/json" \
-d '{"recordId": "123", "data": {...}}'Response:
{
"status": "accepted"
}Environment Variables
Cloudflare Bindings (wrangler.toml)
| Binding | Type | Description |
|---|---|---|
INTEG_DB | D1 | Database for credentials and data |
INTEG_KV | KV | KV storage for caching |
Global Secrets (Doppler)
| Variable | Description |
|---|---|
CRYPTO_KEY | Encryption key for credentials |
CRYPTO_SALT | Salt for encryption |
Integration Secrets (D1 creds table)
Configure your CRM-specific secrets in src/handlers/setup.ts:
const REQUIRED_SECRETS: (keyof ISlavaCredentials)[] = [
"API_KEY",
"API_SECRET",
"WEBHOOK_URL",
];Implementing CRM Integration
1. Define Credentials
Edit src/types.ts:
export interface ISlavaCredentials {
API_KEY: string;
API_SECRET: string;
FOLDER_ID: string;
}2. Update Setup Handler
Edit src/handlers/setup.ts:
const REQUIRED_SECRETS: (keyof ISlavaCredentials)[] = [
"API_KEY",
"API_SECRET",
"FOLDER_ID",
];3. Implement Init Webhook
Edit src/handlers/init-webhook.ts:
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:
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:
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
# 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
- Run
/setupendpoint first - Fill in secrets via integ-admin → Secrets page
- Run
/healthto verify
Database table not found
- Ensure migrations are defined in
src/migrations/index.ts - Run
/setupendpoint to apply migrations
Related Documentation
- INTEGRATION_CHECKLIST.md — Step-by-step guide
- SECRETS.md — Managing secrets
- DEVELOPMENT.md — Local development
- CODE_RULES.md — Coding standards