Canvas SDK
Build custom widgets that extend your workspace. Display dynamic content from your systems directly in Drop Cowboy using webhooks and our flexible Canvas JSON format.
How Canvas Works
Canvas widgets display in designated slots throughout your Drop Cowboy workspace. When a slot is triggered, Drop Cowboy sends context data to your webhook. Your server responds with Canvas JSON that renders in the slot.
1. User Views Contact
User opens a contact detail page in Drop Cowboy
2. Webhook Triggered
Drop Cowboy POSTs contact data to your webhook URL
3. Your Server Responds
Return Canvas JSON within 20 seconds
4. Widget Renders
Canvas JSON is rendered in the slot
Available Slots
Widgets can render in these designated areas of your workspace
Contact Detail
Displays in the contact sidebar when viewing a contact. Perfect for showing CRM data, lead scores, or external system info.
Inbox
Appears in the conversation inbox for contextual actions during messaging.
Dialer
Shows during active calls for call scripts, disposition forms, or real-time data.
Dashboard
Adds custom dashboard widgets for team-wide visibility.
Webhook Request Format
Drop Cowboy sends this payload when a widget slot is triggered
// Headers
{
"Content-Type": "application/json",
"X-DC-Signature": "sha256=abc123...", // HMAC-SHA256 of body
"X-DC-Timestamp": "1704672000",
"X-DC-Widget-Id": "wgt_abc123"
}
// Body
{
"event": "canvas.render",
"widget_id": "wgt_abc123",
"slot": "contact_detail",
"context": {
"contact_id": "cnt_xyz789",
"phone": "+15551234567",
"email": "john@example.com",
"first_name": "John",
"last_name": "Doe",
"custom_fields": {
"company": "Acme Corp",
"lead_score": 85
},
"lists": ["Hot Leads", "Q1 Prospects"],
"tags": ["VIP", "Decision Maker"]
},
"user": {
"user_id": "usr_abc123",
"email": "agent@company.com",
"name": "Sarah Agent"
},
"team_id": "team_abc123",
"timestamp": "2024-01-07T20:00:00Z"
} Verify Webhook Signatures
Always verify the X-DC-Signature header using your widget's signing secret. Compute HMAC-SHA256 of the request body and compare.
Canvas JSON Response
Your server must respond within 20 seconds with a JSON object describing the UI to render
{
"canvas": {
"content": {
"components": [
{
"type": "text",
"text": "Lead Score: 85",
"style": "header"
},
{
"type": "divider"
},
{
"type": "text",
"text": "Last Activity: 2 hours ago",
"style": "muted"
},
{
"type": "spacer",
"size": "medium"
},
{
"type": "list",
"items": [
{ "type": "item", "title": "Pipeline Stage", "subtitle": "Qualified Lead" },
{ "type": "item", "title": "Deal Value", "subtitle": "$25,000" },
{ "type": "item", "title": "Next Follow-up", "subtitle": "Tomorrow 2pm" }
]
},
{
"type": "spacer",
"size": "medium"
},
{
"type": "button",
"label": "View in CRM",
"style": "primary",
"action": {
"type": "url",
"url": "https://crm.example.com/contact/12345"
}
},
{
"type": "button",
"label": "Update Status",
"style": "secondary",
"action": {
"type": "submit",
"payload": {
"action": "update_status",
"contact_id": "12345"
}
}
}
]
}
}
} Available Components
Use these components to build your Canvas UI
text
Display text with optional styles: header, body, muted, caption
{
"type": "text",
"text": "Hello World",
"style": "header"
} divider
Horizontal line to separate content
{
"type": "divider"
} spacer
Vertical spacing: small, medium, large
{
"type": "spacer",
"size": "medium"
} button
Clickable button with action
{
"type": "button",
"label": "Click Me",
"style": "primary",
"action": { ... }
} list
List of items with title/subtitle
{
"type": "list",
"items": [
{ "type": "item",
"title": "Key",
"subtitle": "Value" }
]
} input
Text input field for forms
{
"type": "input",
"id": "notes",
"label": "Notes",
"placeholder": "Enter..."
} dropdown
Select dropdown with options
{
"type": "dropdown",
"id": "status",
"label": "Status",
"options": [
{ "value": "new", "label": "New" }
]
} badge
Colored badge/tag for status
{
"type": "badge",
"text": "VIP",
"color": "green"
} Handling Actions
When users click buttons with submit actions, Drop Cowboy sends a callback to your webhook
User Clicks Button
Button with action.type: "submit" is clicked
Webhook Receives Action
Drop Cowboy POSTs action payload to your webhook
Process & Respond
Your server processes the action and returns new Canvas JSON
// POST to your webhook URL
{
"event": "canvas.action",
"widget_id": "wgt_abc123",
"slot": "contact_detail",
"action": {
"type": "submit",
"payload": {
"action": "update_status",
"contact_id": "12345"
}
},
"input_values": {
"notes": "Spoke with decision maker",
"status": "qualified"
},
"context": { ... }, // Same contact context
"user": { ... }, // Same user context
"timestamp": "2024-01-07T20:05:00Z"
} Complete Example
Express.js server handling Canvas webhook requests
import express from 'express';
import crypto from 'crypto';
const app = express();
app.use(express.json());
const WIDGET_SECRET = process.env.DC_WIDGET_SECRET;
// Verify webhook signature
function verifySignature(req) {
const signature = req.headers['x-dc-signature'];
const timestamp = req.headers['x-dc-timestamp'];
const body = JSON.stringify(req.body);
const expected = crypto
.createHmac('sha256', WIDGET_SECRET)
.update(timestamp + '.' + body)
.digest('hex');
return 'sha256=' + expected === signature;
}
// Canvas webhook endpoint
app.post('/canvas/webhook', async (req, res) => {
// Verify signature
if (!verifySignature(req)) {
return res.status(401).json({ error: 'Invalid signature' });
}
const { event, context, action, input_values } = req.body;
// Handle initial render
if (event === 'canvas.render') {
// Fetch data from your CRM
const crmData = await fetchFromCRM(context.contact_id);
return res.json({
canvas: {
content: {
components: [
{ type: 'text', text: 'CRM Data', style: 'header' },
{ type: 'divider' },
{
type: 'list',
items: [
{ type: 'item', title: 'Pipeline', subtitle: crmData.pipeline },
{ type: 'item', title: 'Deal Value', subtitle: crmData.dealValue }
]
},
{ type: 'spacer', size: 'medium' },
{
type: 'dropdown',
id: 'status',
label: 'Update Status',
options: [
{ value: 'new', label: 'New Lead' },
{ value: 'contacted', label: 'Contacted' },
{ value: 'qualified', label: 'Qualified' }
]
},
{ type: 'spacer', size: 'small' },
{
type: 'button',
label: 'Save',
style: 'primary',
action: {
type: 'submit',
payload: { action: 'update_status' }
}
}
]
}
}
});
}
// Handle button actions
if (event === 'canvas.action') {
if (action.payload.action === 'update_status') {
await updateCRMStatus(context.contact_id, input_values.status);
return res.json({
toast: {
type: 'success',
message: 'Status updated to ' + input_values.status
}
});
}
}
res.status(400).json({ error: 'Unknown event' });
});
app.listen(3000, () => {
console.log('Canvas webhook server running on port 3000');
}); Error Handling
How Drop Cowboy handles webhook errors
Timeout (20 seconds)
If your webhook doesn't respond within 20 seconds, an error message is displayed in the slot.
Invalid JSON
If your response isn't valid Canvas JSON, a parsing error is shown to the user.
HTTP Errors
Non-2xx responses display the error status. Return 200 even with empty canvas.
Retry Behavior
Actions are not automatically retried. A "Retry" button appears for failed renders.
Additional Resources
Ready to build with Canvas?
Create your first widget and extend Drop Cowboy with custom functionality from your systems.