Custom Endpoint
Integrate your own conversational engine with Virbe using the Custom Endpoint service.
The Custom Endpoint integration allows you to connect Virbe to any HTTP endpoint you control. This means you can use any AI/LLM backend – OpenAI, Anthropic, your own fine-tuned models, or entirely custom logic – as the conversational engine behind your Virbe assistant.
How It Works
- A user sends a message to your Virbe assistant
- Virbe Core forwards the message as a POST request to your configured endpoint
- Your endpoint processes the message and returns one or more response actions
- Virbe renders the response (text, UI components, animations, etc.)
Your endpoint can respond in one of two modes:
- Stream – Server-Sent Events (SSE) for real-time, token-by-token or action-by-action delivery
- Batch – A single JSON array containing all response actions at once
Setting Up in Virbe Dashboard
- Navigate to Configurations → Conversational Engines
- Select Custom Endpoint as the engine type
- Configure the following:
- Endpoint URL – The base URL of your server (e.g.
https://your-server.com/conversation/message) - Bearer Token (optional) – An authentication token sent in the
Authorizationheader
- Endpoint URL – The base URL of your server (e.g.
Virbe automatically appends /stream or /batch to your base URL depending on the response mode configured for the profile.
Request Format
When a user sends a message, Virbe POSTs a JSON payload to your endpoint with the following structure:
{
"id": "uuid", // Unique message ID
"conversationId": "uuid", // Conversation session ID
"participantId": "uuid", // ID of the message sender
"participantType": "EndUser", // Sender type
"action": {
"text": {
"text": "User message here", // The user's message
"language": "en" // Message language
}
},
"instant": "2025-01-01T00:00:00.000Z", // Timestamp
"profile": { // Virbe profile metadata
"id": "profile-id",
"languages": ["en", "es"]
},
"conversation": { // Conversation metadata
"id": "uuid"
},
"language": "en" // Current conversation language
}Response Formats
Stream Mode
Return responses with Content-Type: text/event-stream. Each action is sent as a separate SSE event:
Content-Type: text/event-stream
data: {"engineEvent":{"state":"ProcessingStarted","flowId":"flow-123","nodeId":"node-456"}}
data: {"text":{"text":"Hello! How can I help you?","language":"en"}}
data: {"engineEvent":{"state":"ProcessingCompleted","flowId":"flow-123","nodeId":"node-456","elapsedTime":1500}}Each event must be prefixed with data: and followed by two newlines (\n\n).
Batch Mode
Return a JSON array of actions with Content-Type: application/json:
[
{
"engineEvent": {
"state": "ProcessingStarted"
}
},
{
"text": {
"text": "Hello! How can I help you?",
"language": "en"
}
},
{
"engineEvent": {
"state": "ProcessingCompleted",
"elapsedTime": 100
}
}
]Action Types
Each response action is a JSON object with one of the following keys. You can return multiple actions in a single response.
Text Action
The primary way to send a text response back to the user.
{
"text": {
"text": "The response text",
"raw": "Optional raw/unformatted text",
"html": "Optional <b>HTML</b> formatted text",
"ssml": "<speak>Optional SSML for speech synthesis</speak>",
"language": "en"
}
}Only text is required. Use ssml to control how text-to-speech pronounces the response.
Engine Event
Signals the processing state of your engine. Virbe uses these to show loading indicators and track performance.
{
"engineEvent": {
"state": "ProcessingStarted",
"flowId": "flow-id",
"nodeId": "node-id",
"elapsedTime": 1000,
"llmCallName": "optional-call-name",
"metaData": {}
}
}Supported states:
ProcessingStarted– Engine began processing the messageNodeProcessing– A specific node is being processedLlmCallStarted– An LLM API call has startedLlmCallEnded– An LLM API call has completedLlmCallError– An LLM API call has failedProcessingCompleted– Engine finished processing
Your endpoint should always send ProcessingStarted at the beginning and ProcessingCompleted at the end of each response.
Tool Call
Reports tool/function calls made during processing, useful for transparency and debugging.
{
"toolCall": {
"id": "tool-call-1",
"name": "get_weather",
"toolId": "tool-identifier",
"args": { "location": "San Francisco" },
"result": { "temperature": 72, "condition": "sunny" },
"error": null,
"type": "function"
}
}Custom Action
Send arbitrary named actions with flexible payloads for application-specific behavior.
{
"customAction": {
"name": "show_notification",
"value": { "message": "Processing complete!" }
}
}Signal
Emit named events that can be consumed by conversation flows or client applications.
{
"signal": {
"name": "signal-name",
"value": "optional-value"
}
}Variable Store
Store key-value pairs in the conversation context for use in subsequent messages or flows.
{
"variableStore": {
"key": "user_preference",
"value": "dark_mode"
}
}UI Action
Render interactive UI components in the client. Supports buttons, cards, input fields, and web views.
Buttons:
{
"uiAction": {
"name": "virbe-payload-v3",
"value": {
"type": "buttons",
"timeoutMs": 10000,
"buttons": [
{ "id": "btn-yes", "label": "Yes" },
{ "id": "btn-no", "label": "No" }
]
}
}
}Cards:
{
"uiAction": {
"name": "virbe-payload-v3",
"value": {
"type": "cards",
"timeoutMs": 15000,
"cards": [
{
"id": "card-1",
"title": "Product Name",
"imageUrl": "https://example.com/image.jpg",
"payloadType": "text",
"payload": "I want this product"
}
]
}
}
}Input field:
{
"uiAction": {
"name": "virbe-payload-v3",
"value": {
"type": "input",
"input": {
"storeKey": "user_email",
"inputLabel": "Enter your email",
"inputType": "email",
"submitButton": { "id": "submit", "label": "Submit" },
"cancelButton": { "id": "cancel", "label": "Cancel" }
}
}
}
}Web view:
{
"uiAction": {
"name": "virbe-payload-v3",
"value": {
"type": "webView",
"webView": {
"url": "https://example.com/embed",
"fullscreen": false,
"transparent": false,
"persistOnConversationStop": false
}
}
}
}Behavior Action
Trigger avatar animations including gestures and facial expressions.
{
"behaviorAction": {
"name": "virbe-payload-v3",
"value": {
"gestures": [
{ "name": "wave", "start": 0, "duration": 2000 }
],
"emotions": [
{ "name": "happy", "start": 0, "duration": 3000 }
]
}
}
}Flow Event
Signal conversation flow state transitions.
{
"flowEvent": {
"type": "start",
"exitStatus": "success",
"exitDetails": "optional details",
"fromFlowId": "flow-a",
"toFlowId": "flow-b"
}
}Reference Implementation
A complete working example is available at github.com/VirbeHQ/virbe-api-integration. It includes:
- A Fastify server with both stream and batch endpoints
- Zod schemas (
@virbe/dtos) for type-safe request/response validation - Full TypeScript types for all action DTOs
Quick Start
# Clone the repository
git clone https://github.com/VirbeHQ/virbe-api-integration.git
cd virbe-api-integration
# Install dependencies
pnpm install
# Start the development server (API on port 3000, docs on port 4000)
turbo devTesting
# Test streaming (SSE) mode
tsx test-conversation-endpoint.ts sse
# Test batch (JSON) mode
tsx test-conversation-endpoint.ts jsonIntegration with Virbe CustomEndpointService
Under the hood, Virbe's CustomEndpointService handles communication with your endpoint:
const engine = {
credentials: {
url: 'https://your-server.com/conversation/message',
token: 'optional-bearer-token',
},
configuration: {
// Custom configuration passed to your endpoint
},
};The service will:
- Send a POST request with conversation data to your URL
- Automatically detect the response type based on the
Content-Typeheader - Parse and process actions from either SSE events or JSON arrays
- Render the actions in the Virbe client
Make sure your endpoint returns the correct Content-Type header: text/event-stream for streaming or application/json for batch responses. Virbe uses this header to determine how to parse the response.