Skip to content

Webhooks API

EspoCRM sends webhooks to the Bridge API when entities are created, updated, or deleted.

Overview

Webhook Endpoints

Guest Booking Webhooks

EndpointTriggerDescription
POST /webhooks/guest-createdGuestBooking createdNew guest added
POST /webhooks/guest-updatedGuestBooking updatedStage or data changed

Content Webhooks

EndpointTriggerDescription
POST /webhooks/content-createdContentProduction createdNew content added
POST /webhooks/content-updatedContentProduction updatedStage or data changed

Platform Webhooks

EndpointTriggerDescription
POST /webhooks/platform-createdPlatformPublish createdNew platform target added
POST /webhooks/platform-updatedPlatformPublish updatedTriggers publishing when status=Queued

Clip Webhooks

EndpointTriggerDescription
POST /webhooks/clip-createdClip createdNew clip added
POST /webhooks/clip-updatedClip updatedStatus changed

Social Post Webhooks

EndpointTriggerDescription
POST /webhooks/social-createdSocialPost createdSchedules via Ayrshare
POST /webhooks/social-updatedSocialPost updatedContent or schedule changed
POST /webhooks/social-deletedSocialPost deletedCancels in Ayrshare

Payload Format

EspoCRM sends the full entity data:

json
{
  "id": "entity-id-123",
  "name": "Entity Name",
  "status": "Published",
  "createdAt": "2026-02-05T12:00:00.000Z",
  "modifiedAt": "2026-02-05T14:30:00.000Z",
  // ... all entity fields
}

Configuring Webhooks in EspoCRM

Create Webhook

  1. Go to Admin → Webhooks
  2. Click Create Webhook
  3. Configure:
FieldValue
URLhttp://bridge:3100/webhooks/guest-created
Entity TypeGuestBooking
EventAfter Create
Is ActiveYes
  1. Save

Webhook Events

EventDescription
After CreateEntity created
After UpdateEntity updated
After DeleteEntity deleted
After SaveCreate or Update

URL Format

Use the Docker internal hostname:

http://bridge:3100/webhooks/{entity}-{event}

Examples:

  • http://bridge:3100/webhooks/guest-created
  • http://bridge:3100/webhooks/platform-updated
  • http://bridge:3100/webhooks/social-deleted

Webhook Processing

Guest Updated Handler

javascript
router.post('/guest-updated', async (req, res) => {
  const guest = req.body;
  
  // Trigger n8n workflow based on stage
  const stageWorkflows = {
    'Confirmed': 'guest-confirmed',
    'Scheduled': 'guest-scheduled',
    'Recorded': 'guest-recorded',
    'Published': 'guest-published',
  };
  
  const workflow = stageWorkflows[guest.stage];
  if (workflow) {
    await triggerN8nWorkflow(workflow, guest);
  }
  
  res.json({ success: true });
});

Platform Updated Handler

javascript
router.post('/platform-updated', async (req, res) => {
  const platform = req.body;
  
  // Only process if status changed to Queued
  if (platform.status !== 'Queued') {
    return res.json({ success: true, skipped: true });
  }
  
  try {
    // Get platform route
    const route = PLATFORM_ROUTES[platform.platform];
    const endpoint = platform.publishType === 'Livestream' 
      ? route.livestream 
      : route.upload;
    
    // Forward to X Server
    const result = await xServerClient.post(endpoint, {
      title: platform.title,
      description: platform.description,
      thumbnailUrl: platform.thumbnailUrl,
      scheduledAt: platform.scheduledAt,
      // ... other fields
    });
    
    // Update EspoCRM with result
    await espoClient.update('PlatformPublish', platform.id, {
      status: 'Published',
      platformUrl: result.platformUrl,
      platformId: result.platformId,
      streamUrl: result.streamUrl,
      streamKey: result.streamKey,
    });
    
    // Check for dependent platforms
    await handleDependentPlatforms(platform, result);
    
    res.json({ success: true });
  } catch (error) {
    // Update with error
    await espoClient.update('PlatformPublish', platform.id, {
      status: 'Failed',
      apiResponse: JSON.stringify(error.message),
    });
    
    res.status(500).json({ error: error.message });
  }
});

Social Created Handler

javascript
router.post('/social-created', async (req, res) => {
  const post = req.body;
  
  // Skip drafts
  if (post.status !== 'Scheduled') {
    return res.json({ success: true, skipped: true });
  }
  
  try {
    // Schedule via Ayrshare
    const result = await scheduleAyrsharePost({
      post: post.content,
      platforms: [post.platform.toLowerCase()],
      scheduleDate: post.scheduledAt,
      mediaUrls: post.mediaUrl ? [post.mediaUrl] : [],
    });
    
    // Update with Ayrshare ID
    await espoClient.update('SocialPost', post.id, {
      ayrshareId: result.id,
    });
    
    res.json({ success: true, ayrshareId: result.id });
  } catch (error) {
    await espoClient.update('SocialPost', post.id, {
      status: 'Failed',
      error: error.message,
    });
    
    res.status(500).json({ error: error.message });
  }
});

Social Deleted Handler

javascript
router.post('/social-deleted', async (req, res) => {
  const post = req.body;
  
  // Cancel in Ayrshare if scheduled
  if (post.ayrshareId) {
    await deleteAyrsharePost(post.ayrshareId);
  }
  
  res.json({ success: true });
});

Testing Webhooks

Manual Test

bash
curl -X POST http://localhost:3100/webhooks/test \
  -H "Content-Type: application/json" \
  -d '{"test": true}'

Simulate Entity Event

bash
curl -X POST http://localhost:3100/webhooks/guest-updated \
  -H "Content-Type: application/json" \
  -d '{
    "id": "test-123",
    "name": "Test Guest",
    "stage": "Confirmed",
    "email": "test@example.com"
  }'

Debugging

Check Bridge Logs

bash
docker compose logs bridge | grep webhooks

Verify Webhook Received

Add logging to webhook handlers:

javascript
router.post('/guest-updated', async (req, res) => {
  console.log('Received guest-updated:', JSON.stringify(req.body, null, 2));
  // ... handler code
});

Common Issues

Webhook not reaching Bridge
  1. Check webhook URL uses http://bridge:3100 (internal Docker hostname)
  2. Verify webhook is active in EspoCRM
  3. Check Bridge is running: docker compose ps bridge
Webhook received but not processed
  1. Check Bridge logs for errors
  2. Verify entity data matches expected format
  3. Check conditional logic (e.g., status checks)

MediaMagic CRM Documentation