Zoom Phone (VoIP) Integration for Attio
Keep Zoom Phone as the dialer and Attio as the place where CRM context lives. This custom app pattern brings in-scope call, SMS, recording, voicemail-related, and meeting-created context into Attio without asking reps to log touchpoints by hand. Zoom sends subscribed events to a webhook receiver, the receiver verifies x-zm-signature, handles the endpoint.url_validation challenge, and writes to Attio idempotently. Attio gets the operational context your team needs while Zoom Phone remains the system that owns calling, messaging, recordings, and Phone administration.
Who this is for
This build is for teams that run calling and SMS in Zoom Phone while managing customers, pipeline, and account work in Attio. It fits when Zoom Phone is the dialer and communications system, while Attio is where sales, support, or account teams need the history of recent touchpoints.
It is also a fit when manual logging is creating gaps. The build can surface call status changes, sent and received SMS, completed recordings, recording URLs, and meeting-created metadata in Attio, with the exact event coverage and fields agreed during scope.
How the sync works
The sync has three parts: verified Zoom webhook intake, Attio writes keyed by stable Zoom identifiers, and optional historical REST reads for support and reconciliation.
Webhook flow
Zoom sends Phone and Meeting webhook deliveries with a wrapper that includes event, event_ts, and payload. Phone payloads include payload.account_id and a payload.object containing the event-specific call, SMS, or recording data.
Before processing, the receiver verifies x-zm-signature using x-zm-request-timestamp, the raw request body, and the webhook secret token. During setup and periodic revalidation, Zoom sends endpoint.url_validation; the receiver answers with the received plainToken and an encryptedToken HMAC. Successful webhook responses return 200 or 204 within 3 seconds.
Attio write shape
The standard destination is a custom Attio object such as zoom_calls. Call-status events upsert by Zoom call_id. SMS events use message identifiers from the SMS payload or historical REST read in scope. Recording events use recording IDs, so repeated deliveries update the existing Attio record instead of creating duplicates.
The standard attribute set includes timestamps, direction, caller and callee phone numbers, caller and callee user IDs when Zoom provides them, recording URLs for recording events, message content for SMS, and meeting metadata for meeting.created. We decide the exact field map with you before installation.
Direction detection
SMS direction comes from the event name. phone.sms_sent is outbound, and phone.sms_received is inbound.
For Phone status events, direction is an implementation pattern, not a Zoom guarantee. When payload.object.caller.user_id is present, the call is treated as outbound from your tenant. When payload.object.callee.user_id is present, the call is treated as inbound. Zoom does not document both caller.user_id and callee.user_id as required on every status event, so the build also uses scoped phone-number fallback rules when user IDs are missing.
What we configure during scope
The scoping call turns the Zoom-Phone-to-Attio pattern into your build. These decisions are made before the app is installed.
| Decision | What we decide with you |
|---|---|
| Zoom tenant and license | Which Zoom tenant connects to Attio, whether Zoom Phone is enabled, and which admin or service account owns the app configuration. |
| Event coverage | Which of the ten Phone events and whether meeting.created should feed Attio. |
| Attio destination object | Whether the build writes to zoom_calls or another scoped Attio object, and how that surface should appear to users. |
| Field map | Which timestamps, identifiers, phone numbers, user IDs, SMS fields, recording fields, and meeting metadata should be visible in Attio. |
| Direction and E.164 handling | How inbound and outbound direction is inferred, which tenant phone numbers are trusted for fallback logic, and how our E.164 validation handles values before write. |
| Recordings and transcripts | Whether recording URLs, voicemail-related records, and recording transcript reads belong in the Attio surface or only in support workflows. |
| Support actions | Which record-level resync, bulk resync, reprovisioning, and reconciliation controls should be available to operators. |
Why this works with the Zoom Phone API
Zoom documents a Phone webhook surface for call status, SMS/MMS, recordings, and other Phone events in the Phone Webhooks reference. The related Zoom Phone call-data guide groups the call status events this pattern uses, including caller ringing, caller connected, callee answered, caller ended, callee ended, missed, and rejected events.
The webhook security model is documented in Zoom's Using webhooks guide. Zoom sends x-zm-signature and x-zm-request-timestamp. The receiver computes an HMAC SHA-256 over v0:{timestamp}:{body} with the webhook secret token, prepends v0=, and compares the result with the signature header before processing.
The same webhook guide documents the endpoint.url_validation handshake. Zoom sends a body containing payload.plainToken, event_ts, and event. The receiver responds with the same plainToken and an encryptedToken HMAC SHA-256 hash of that token using the webhook secret token.
Historical and support reads use the Phone APIs, not webhook replay. Zoom Phone REST API docs include call-history, SMS, voicemail, and recording-related surfaces, while the granular scopes docs list scopes such as phone:read:sms_message:admin, phone:read:recording_transcript:admin, and phone:read:voicemail:admin. For call data, the validated source packet uses documented call-log scopes such as phone:read:admin or phone:read:list_call_logs:admin; it does not verify phone:read:call:admin.
Rate limits shape backfill and reconciliation. Zoom's rate limit guide uses Light, Medium, Heavy, and Resource-intensive categories, and Zoom Phone Pro and Business+ have distinct quotas. Meeting creation context comes from the Meeting webhooks reference when meeting.created is in scope.
How reliability is handled
Webhooks are verified by x-zm-signature before processing. Zoom also revalidates production and development webhook URLs every 72 hours. After 6 consecutive failed revalidations, Zoom stops sending webhook events and disables the event subscription.
Zoom documents bounded retries for qualifying failures: 3 retries after the initial delivery attempt, at 5 minutes, 20 minutes, and 60 minutes. If those retries fail, Zoom sends no further webhook for that event. The scoped docs do not state at-most-once, at-least-once, or exactly-once delivery.
Attio writes are idempotent by call_id, message ID, or recording ID. No Phone webhook replay API was found in the scoped Zoom docs, so recovery and reconciliation use Zoom Phone historical REST endpoints for call history, SMS sessions, voicemail, and recording transcripts where those reads are in scope.
How setup works
Setup keeps authorization and webhook authenticity separate. The Zoom Marketplace app is configured for the tenant, OAuth grants only the granular scopes the app needs, endpoint.url_validation proves the receiver can answer Zoom's challenge, and webhook events begin after the subscription is saved.
- Scope the Zoom Phone and Attio model. We confirm the Zoom tenant, Zoom Phone license, which events are in scope, the Attio destination object, the field map, and how records should match.
- Build and authorize the Zoom Marketplace app via OAuth. The Zoom app requests the granular scopes needed for SMS, voicemail, recording transcripts, and call data. Call-data access uses documented call-log scopes such as phone:read:admin or phone:read:list_call_logs:admin, depending on the historical reads in scope.
- Configure the event subscription endpoint and pass URL validation. The event subscription endpoint responds to Zoom's endpoint.url_validation handshake with the received plainToken and the encryptedToken HMAC response Zoom requires.
- Subscribe to the agreed Phone events plus meeting.created. The Zoom Marketplace app subscribes to the scoped Phone events and meeting.created. Every webhook delivery is verified with x-zm-signature before processing.
- Install the Attio app and provision the destination object. We install the custom Attio app, provision the agreed zoom_calls object or destination surface, apply the field map, and add the record, bulk, and support controls in scope.
- Turn on live sync. After the endpoint is validated and the subscriptions are saved, live webhook events write to Attio. Record-level and bulk resync remain available for support and reconciliation.
Known limitations
Custom app, not a Zoom marketplace install
This is a scoped custom engagement. It is not a public Zoom marketplace app, not an Attio Marketplace app, and not a one-click install.
Requires Zoom Phone on the tenant
Zoom Phone API access requires Zoom Phone to be enabled for the account. Accounts without Zoom Phone receive a documented 403 response for Phone API access.
Webhooks are not the historical backfill path
Webhooks handle new deliveries after the event subscription is active. Historical records are pulled through Zoom Phone REST APIs when call history, SMS, voicemail, recording, or transcript backfill is in scope.
No Phone webhook replay API
The scoped Zoom docs do not document a Phone webhook replay API for missed events. The build uses idempotent writes and REST-backed reconciliation instead of assuming Zoom can resend old Phone webhooks.
E.164 validation is our implementation
Zoom documents some Phone webhook fields as E164, but the scoped docs do not prove that every Phone payload always emits normalized E.164 numbers. We validate and normalize phone numbers in the integration before writing fields that need that format.
Webhook subscription is configured manually
The agreed Phone events and meeting.created are configured in the Zoom Marketplace app for the tenant. This is part of setup, not a self-serve toggle inside Attio.
Caller and callee user IDs are not guaranteed on every event
Zoom Phone status payloads include caller and callee objects, and user IDs appear on relevant participant objects for several events. The scoped docs do not document both user IDs as required on every status event, so direction logic needs a phone-number fallback.
Person linking is a separate Attio workflow
The zoom_calls surface stores call, SMS, recording, and meeting context. Matching those records to Attio People by phone number is a separate workflow or scope item.
Frequently asked questions
Is this a public Zoom marketplace app?
No. This is a client-proven Zoom-Phone-to-Attio custom app pattern, not a public Zoom marketplace app or a one-click install. We scope the Zoom tenant, Phone events, Attio schema, OAuth scopes, and support actions before setup.
What Zoom Phone data flows into Attio?
The standard pattern writes call status, SMS, recording, and meeting-created context into a narrow Attio surface. Typical attributes include timestamps, direction, caller and callee phone numbers, caller and callee user IDs when Zoom provides them, recording URLs for recording events, SMS message content, and meeting metadata for meeting.created.
Which Zoom webhook events are used?
The scoped Phone event set can include phone.caller_ringing, phone.caller_connected, phone.callee_answered, phone.caller_ended, phone.callee_ended, phone.callee_missed, phone.callee_rejected, phone.sms_sent, phone.sms_received, and phone.recording_completed. The build can also subscribe to meeting.created when meeting creation context belongs in Attio.
Does the integration backfill historical calls?
Webhooks are not the historical backfill path. Zoom does not document a Phone webhook replay API for missed events. When historical data is in scope, resync uses Zoom Phone REST surfaces such as call history, SMS sessions, voicemail, and recording transcript endpoints rather than replaying old webhook deliveries.
How is webhook security handled?
Zoom webhook requests are verified with x-zm-signature before processing. The receiver computes an HMAC SHA-256 signature over v0:{timestamp}:{body} using the webhook secret token and compares it with the header. The endpoint.url_validation handshake also requires a plainToken and encryptedToken response before subscriptions can send events.
How is inbound vs outbound determined?
SMS direction comes from the event name: phone.sms_sent is outbound and phone.sms_received is inbound. For Phone status events, the implementation checks whether caller.user_id or callee.user_id is present, then falls back to scoped phone-number rules when user IDs are missing. Zoom does not guarantee both caller.user_id and callee.user_id on every status event.
Can we choose which events sync?
Yes. Event coverage is a scope decision. We decide which of the ten Phone events and whether meeting.created should feed Attio, then configure the Zoom Marketplace event subscription for that tenant.
What happens if a Zoom webhook fails?
Zoom documents bounded retries for qualifying failures: three retries after the initial attempt at 5 minutes, 20 minutes, and 60 minutes. After that, Zoom sends no further webhook for that event. The Attio writes are idempotent by call_id, message ID, or recording ID, and support resync uses Zoom Phone historical REST endpoints when reconciliation is needed.