Webhooks API
EspoCRM sends webhooks to the Bridge API when entities are created, updated, or deleted.
Overview
Webhook Endpoints
Guest Booking Webhooks
| Endpoint | Trigger | Description |
|---|---|---|
POST /webhooks/guest-created | GuestBooking created | New guest added |
POST /webhooks/guest-updated | GuestBooking updated | Stage or data changed |
Content Webhooks
| Endpoint | Trigger | Description |
|---|---|---|
POST /webhooks/content-created | ContentProduction created | New content added |
POST /webhooks/content-updated | ContentProduction updated | Stage or data changed |
Platform Webhooks
| Endpoint | Trigger | Description |
|---|---|---|
POST /webhooks/platform-created | PlatformPublish created | New platform target added |
POST /webhooks/platform-updated | PlatformPublish updated | Triggers publishing when status=Queued |
Clip Webhooks
| Endpoint | Trigger | Description |
|---|---|---|
POST /webhooks/clip-created | Clip created | New clip added |
POST /webhooks/clip-updated | Clip updated | Status changed |
Social Post Webhooks
| Endpoint | Trigger | Description |
|---|---|---|
POST /webhooks/social-created | SocialPost created | Schedules via Ayrshare |
POST /webhooks/social-updated | SocialPost updated | Content or schedule changed |
POST /webhooks/social-deleted | SocialPost deleted | Cancels 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
- Go to Admin → Webhooks
- Click Create Webhook
- Configure:
| Field | Value |
|---|---|
| URL | http://bridge:3100/webhooks/guest-created |
| Entity Type | GuestBooking |
| Event | After Create |
| Is Active | Yes |
- Save
Webhook Events
| Event | Description |
|---|---|
| After Create | Entity created |
| After Update | Entity updated |
| After Delete | Entity deleted |
| After Save | Create or Update |
URL Format
Use the Docker internal hostname:
http://bridge:3100/webhooks/{entity}-{event}Examples:
http://bridge:3100/webhooks/guest-createdhttp://bridge:3100/webhooks/platform-updatedhttp://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 webhooksVerify 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
- Check webhook URL uses
http://bridge:3100(internal Docker hostname) - Verify webhook is active in EspoCRM
- Check Bridge is running:
docker compose ps bridge
Webhook received but not processed
- Check Bridge logs for errors
- Verify entity data matches expected format
- Check conditional logic (e.g., status checks)