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

FeatureBuilding integrationTenant integration
Configured byBuilding admin (e.g., property manager)Tenant (resident or unit owner)
Event scopeEvery event in the buildingOnly events tied to the tenant's unit or access tools
API endpoint/v4/buildings/{building_id}/integrations/v4/tenants/{tenant_id}/integrations
Typical usesSecurity dashboards, delivery monitoringPersonal apps, smart-home automations
Example eventsAll calls and door releases in the buildingCalls and door releases involving the tenant

🔔 Supported Events

Each integration subscribes to one or more combinations of resource type and action:

Resource typeActionFired when
callcreateA video call is placed
door_releasecreateA 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 call webhook 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

FieldTypeRequiredDescription
urlstringYesThe endpoint that receives events. Use HTTPS.
methodstringYesThe HTTP method used for delivery: post or get.
headersobjectNoCustom headers included with every delivery, as name/value string pairs. See Securing Your Webhook Endpoint.

bindings

Each entry in bindings subscribes the integration to an event.

FieldTypeRequiredDescription
resource_typestringYescall or door_release.
actionsarray of stringsYesCurrently ["create"].
resource_idinteger or nullNoLimits 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 401 if 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, or Host.
⚠️

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

OperationBuildingTenant
ListGET /v4/buildings/{building_id}/integrationsGET /v4/tenants/{tenant_id}/integrations
CreatePOST /v4/buildings/{building_id}/integrationsPOST /v4/tenants/{tenant_id}/integrations
UpdatePUT /v4/buildings/{building_id}/integrations/{id}PUT /v4/tenants/{tenant_id}/integrations/{id}
DeleteDELETE /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 OK within 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.id to deduplicate.
  • Monitor failures. Log failed deliveries and set up alerts so you can fix problems early.

🧪 Testing Tools

⚠️

webhook.site URLs are public. Use dummy header values when testing there, and never production secrets.