Welcome to Drop Cowboy - Ringless Voicemail Platform. Main navigation and content follows.

Developers

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

Available

Contact Detail

Displays in the contact sidebar when viewing a contact. Perfect for showing CRM data, lead scores, or external system info.

Coming Soon

Inbox

Appears in the conversation inbox for contextual actions during messaging.

Coming Soon

Dialer

Shows during active calls for call scripts, disposition forms, or real-time data.

Coming Soon

Dashboard

Adds custom dashboard widgets for team-wide visibility.

Webhook Request Format

Drop Cowboy sends this payload when a widget slot is triggered

POST Request to Your Webhook
// 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 JSON Response
{
  "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

1

User Clicks Button

Button with action.type: "submit" is clicked

2

Webhook Receives Action

Drop Cowboy POSTs action payload to your webhook

3

Process & Respond

Your server processes the action and returns new Canvas JSON

Action Webhook Payload
// 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"
}

Response Options

Update Canvas

Return new Canvas JSON to replace the current widget content

{
  "canvas": {
    "content": { ... }
  }
}

Show Toast

Display a notification message to the user

{
  "toast": {
    "type": "success",
    "message": "Contact updated!"
  }
}

Redirect

Open a URL in a new tab

{
  "redirect": {
    "url": "https://...",
    "target": "_blank"
  }
}

Complete Example

Express.js server handling Canvas webhook requests

Node.js Canvas Webhook Handler
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

API Documentation

Full API reference and OpenAPI spec

View Docs →

Postman Collection

Ready-to-use Canvas examples

Import Collection →

Webhooks Guide

Learn about Drop Cowboy webhooks

Read Guide →

Ready to build with Canvas?

Create your first widget and extend Drop Cowboy with custom functionality from your systems.

Ready to Transform Your Outreach?

Join 20,000+ businesses achieving 5-20x higher response rates with Drop Cowboy®