Webhooks
Learn how to use ButterflyMX's webhook system to receive real-time updates on access events and video calls. This guide explains how to set up and manage webhooks for both building-wide and tenant-specific integrations.
📡 Webhooks via Integrations
ButterflyMX delivers webhooks through integrations. An integration tells ButterflyMX which events to watch and where to send them. Integrations come in two scopes:
- Building integrations are set up by a building administrator and cover the whole building.
- Tenant integrations are set up by an individual tenant and cover only that tenant's unit.
Both scopes send real-time notifications to a third-party system when events such as door releases and video calls happen.
🧭 Building vs. Tenant Integrations
| Feature | Building integration | Tenant integration |
|---|---|---|
| Configured by | Building admin (e.g., property manager) | Tenant (resident or unit owner) |
| Event scope | Every event in the building | Only events tied to the tenant's unit or access tools |
| API endpoint | /v4/buildings/{building_id}/integrations | /v4/tenants/{tenant_id}/integrations |
| Typical uses | Security dashboards, delivery monitoring | Personal apps, smart-home automations |
| Example events | All calls and door releases in the building | Calls and door releases involving the tenant |
🔔 Supported Events
Each integration subscribes to one or more combinations of resource type and action:
| Resource type | Action | Fired when |
|---|---|---|
call | create | A video call is placed |
door_release | create | A door is unlocked (via app, PIN, or QR code) |
🔗 Webhooks and the ButterflyMX SDK
If you integrate the ButterflyMX SDK, you need a call webhook. It is how your application learns that a video call has started, so you can:
- Trigger your own UI flows
- Start the media stream through the SDK
- Log or analyze call activity in real time
Note: The SDK does not open connections on its own. Your backend must receive the
callwebhook and pass the incoming call to your client app. See the ButterflyMX SDK documentation on GitHub for details.
⚙️ Integration Object
All integration requests use the same body structure.
configuration
configuration| Field | Type | Required | Description |
|---|---|---|---|
url | string | Yes | The endpoint that receives events. Use HTTPS. |
method | string | Yes | The HTTP method used for delivery: post or get. |
headers | object | No | Custom headers included with every delivery, as name/value string pairs. See Securing Your Webhook Endpoint. |
bindings
bindingsEach entry in bindings subscribes the integration to an event.
| Field | Type | Required | Description |
|---|---|---|---|
resource_type | string | Yes | call or door_release. |
actions | array of strings | Yes | Currently ["create"]. |
resource_id | integer or null | No | Limits the binding to a single resource. Leave it out or set it to null to receive events for all resources of this type within the integration's scope. |
🏢 Building Integration Setup
POST /v4/buildings/{building_id}/integrations
Sample Request
{
"data": {
"type": "integrations",
"attributes": {
"integrator": "webhook",
"configuration": {
"url": "https://your-webhook-url.com/building-events",
"method": "post"
},
"bindings": [
{
"resource_type": "door_release",
"actions": ["create"]
},
{
"resource_type": "call",
"actions": ["create"]
}
]
}
}
}👤 Tenant Integration Setup
POST /v4/tenants/{tenant_id}/integrations
A tenant integration fires only for events tied to that tenant's own unit or access tools.
Sample Request
{
"data": {
"type": "integrations",
"attributes": {
"integrator": "webhook",
"configuration": {
"url": "https://your-webhook-url.com/tenant-events",
"method": "post"
},
"bindings": [
{
"resource_type": "call",
"actions": ["create"]
}
]
}
}
}🔐 Securing Your Webhook Endpoint
ButterflyMX does not sign webhook payloads. To make sure a request really came from your integration, put a secret in configuration.headers. ButterflyMX sends these headers unchanged with every delivery, and your endpoint rejects any request that is missing the expected value.
Sample Request with Security Headers
{
"data": {
"type": "integrations",
"attributes": {
"integrator": "webhook",
"configuration": {
"url": "https://your-webhook-url.com/building-events",
"method": "post",
"headers": {
"Authorization": "Bearer 3f9c1e7a2b8d4c6f9a0e5b1d7c3a8f2e",
"X-Webhook-Source": "butterflymx-building-123"
}
},
"bindings": [
{
"resource_type": "call",
"actions": ["create"]
}
]
}
}
}Recommendations
- Use a long, random secret. Generate at least 32 random bytes and send them as a bearer token or in a dedicated header such as
X-Webhook-Secret. - Compare it in constant time. Use your language's constant-time comparison when checking the header, and return
401if it doesn't match. - Use HTTPS only. Custom headers are sent in plain text over HTTP, so the secret is protected only by TLS.
- Use a separate secret per integration. You can then revoke one integration without affecting the others.
- Rotate secrets periodically. Update the integration with the new header value, and have your endpoint accept both the old and new values during the switch-over.
- Don't override standard headers. Avoid setting
Content-Type,Content-Length, orHost.
A static header proves the sender knows your secret. It does not protect the payload against modification or replay. For stronger guarantees, combine it with HTTPS, IP filtering, and idempotent event handling.
📦 Webhook Payload Format
Building and tenant integrations use the same payload structure:
{
"event": {
"resource_type": "door_release",
"action": "create",
"data": {
"id": 123456789,
"logged_at": "2025-04-24T10:24:35Z",
"access_point": 22177636,
"entry_method": "Swipe to open",
"release_status": "Unlocked",
"name": "John Tenant",
"image_url": "https://cdn.butterflymx.com/snapshot.png"
}
}
}🔁 Managing Integrations
| Operation | Building | Tenant |
|---|---|---|
| List | GET /v4/buildings/{building_id}/integrations | GET /v4/tenants/{tenant_id}/integrations |
| Create | POST /v4/buildings/{building_id}/integrations | POST /v4/tenants/{tenant_id}/integrations |
| Update | PUT /v4/buildings/{building_id}/integrations/{id} | PUT /v4/tenants/{tenant_id}/integrations/{id} |
| Delete | DELETE /v4/buildings/{building_id}/integrations/{id} | DELETE /v4/tenants/{tenant_id}/integrations/{id} |
✅ Best Practices
- Authenticate deliveries. Check the secret header on every request (see Securing Your Webhook Endpoint). If you need more, add IP filtering on top.
- Respond quickly. Return a
200 OKwithin 5 seconds, or the delivery is retried. Do heavy processing asynchronously, after you've responded. - Handle duplicates. Retries can deliver the same event more than once. Use
event.data.idto deduplicate. - Monitor failures. Log failed deliveries and set up alerts so you can fix problems early.
🧪 Testing Tools
- webhook.site lets you inspect deliveries in real time.
webhook.site URLs are public. Use dummy header values when testing there, and never production secrets.
Updated 6 days ago

