What this enables
When configured, Voce can:
- Check real-time availability on a Microsoft 365 mailbox's calendar during a call
- Book appointments and create events via Microsoft Graph
- Send a calendar invite to the caller's email (if provided)
- Respect per-tenant booking windows so only specific days/hours are offered
tenant_id + client_id + client_secret.Prerequisites
- A Microsoft 365 tenant (your work/business Microsoft account, NOT a personal
@outlook.com) - An Azure AD account with permission to register apps AND grant admin consent on application permissions (typically a Global Admin or Application Administrator role). If you don't have admin rights, your M365 admin must do Steps 2 and 3 for you.
- The mailbox whose calendar Voce should write to (e.g.
[email protected]) - ~10 minutes
Step 1 โ Register an App in Azure AD
- Open https://portal.azure.com
- Search bar (top) โ App registrations โ click it
- + New registration
- Name: something descriptive, e.g.
voce-calendar-integration - Supported account types: "Accounts in this organizational directory only (Single tenant)" โ recommended for security
- Redirect URI: leave blank (not needed for app-only / client-credentials flow)
- Register
- On the resulting overview page, copy these two values โ you'll need them in Step 5:
- Application (client) ID
- Directory (tenant) ID
Step 2 โ Grant the Calendars.ReadWrite (Application) Permission
- From your new app's overview page, left nav โ API permissions
- + Add a permission โ Microsoft Graph โ Application permissions (CRITICAL โ NOT Delegated)
- Search for and check Calendars.ReadWrite
- Add permissions
- Back on the API permissions page, click Grant admin consent for [Your Tenant] (top of the permissions list)
- This button requires admin rights. If you don't see it or it's grayed out, ask your M365 admin to do this step.
- After consent, the status column should show a green checkmark โ
- Verify the row reads:
Microsoft Graph Calendars.ReadWrite Application Granted for [Tenant]
Step 3 โ Create a Client Secret
- From your app's overview page, left nav โ Certificates & secrets
- + New client secret
- Description: e.g.
voce-integration-secret - Expires: pick a lifetime โ 24 months is the longest Azure allows; you'll need to rotate before it expires
- Add
- IMMEDIATELY copy the
Valuecolumn โ this is shown ONCE and never visible again. If you lose it, delete and create a new one. - Do NOT copy the
Secret IDโ that's a different value you don't need.
Step 4 โ Identify the Target Mailbox
The User Principal Name (UPN) is the email-format identifier of the mailbox Voce will read/write. Examples:
[email protected][email protected]- A specific person's mailbox:
[email protected]
Where to find it:
- M365 Admin Center โ Users โ Active users โ click the user โ Username/UPN field
- Or just use the email address โ for most M365 setups, the email and UPN are identical
Step 5 โ Configure in the Voce Dashboard
- Open the Voce tuning dashboard
- Scroll to Integrations ยท Microsoft Calendar credentials
- Fill in:
- Azure Tenant ID โ from Step 1.8 (Directory ID, looks like
xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx) - App Client ID โ from Step 1.8 (Application ID, same UUID format)
- App Client Secret โ from Step 3.6 (the Value, NOT the Secret ID)
- Mailbox (user principal name) โ from Step 4
- Azure Tenant ID โ from Step 1.8 (Directory ID, looks like
- Scroll up to Integrations ยท Calendar backend (which one is active)
- Active calendar backend โ Microsoft Calendar
- Booking Windows (optional but recommended) โ restrict bookable times, e.g.:
Empty = 9-5 M-F default.tue 14:00-17:00, thu 14:00-17:00 - Save
Step 6 โ Verify
Voce will use the new calendar on the next inbound call. The operator can run the smoke script on the Voce VM to validate the plumbing without a phone call (covers token acquisition + free/busy query).
A successful test outputs:
โ
ms_calendar_token_refreshed
โ
free/busy returned N busy ranges for today
โ
End-to-end Microsoft Calendar integration is working.
Common Errors and Fixes
| Error message | What it means | Fix |
|---|---|---|
AADSTS7000215: Invalid client secret provided | Secret is wrong or has expired | Re-check Step 3.6 (the Value, not the Secret ID); if expired, create a new secret and re-paste |
AADSTS700016: Application with identifier ... was not found | Client ID or Tenant ID has a typo | Re-copy both UUIDs from Step 1.8 |
AADSTS65001: The user or administrator has not consented | Application permission not yet admin-consented | Step 2.5 โ admin must click Grant admin consent |
Forbidden / HTTP 403 on /calendar/getSchedule or /events | Calendars.ReadWrite (Application) permission missing or scoped to a different resource | Step 2 โ confirm permission is Application (not Delegated) and shows โ "Granted for [Tenant]" |
ResourceNotFound โ The specified object was not found for the UPN | UPN typo, mailbox doesn't exist, or no Exchange Online license on that user | Verify UPN in M365 Admin Center; confirm the user has an active Exchange Online license |
| Permissions appear granted but calls still fail | Permission propagation lag (rare) | Wait ~5 minutes and retry. If still failing, try removing + re-adding the permission. |
โ ๏ธ Important Security Consideration
Calendars.ReadWrite Application permission can read and write EVERY mailbox in your tenant โ not just the one you specified in the UPN. For most installs, this is acceptable trust (the secret stays secret, the app code only ever uses the configured UPN). For tighter security, you can scope the app to a single mailbox using PowerShell.# Run as an Exchange admin (Exchange Online PowerShell module)
New-DistributionGroup -Name "VoceCalendarAccess" -Type Security
Add-DistributionGroupMember -Identity "VoceCalendarAccess" `
-Member "[email protected]"
New-ApplicationAccessPolicy -AppId <client-id-from-step-1> `
-PolicyScopeGroupId "[email protected]" `
-AccessRight RestrictAccess `
-Description "Restrict Voce to the scheduling mailbox only"
This locks Voce's access to only the [email protected] mailbox. Other mailboxes in the tenant become unreachable to the app even if its secret leaks. Recommended for HIPAA-regulated tenants or anywhere with sensitive mailbox content beyond the scheduling box.
Operational Notes
- Client secrets are long-lived but EXPIRE (default โค24 months). Mark your calendar to rotate before expiry, or you'll get
AADSTS7000215errors on the day it expires and bookings will fail. - No refresh tokens โ Microsoft Graph access tokens (~1hr lifetime) are acquired fresh each time using just
tenant_id + client_id + client_secret. Voce caches them in-process. - Client secret is a long-lived secret โ marked secret in the Voce dashboard, never logged. Treat like a password.
- Permission propagation can lag โ newly-granted permissions sometimes take a couple of minutes to take effect. If a fresh setup fails, wait 5 min and retry before debugging.
- Per-tenant: every tenant onboarding repeats Steps 1โ5 with their own Azure tenant, app registration, and credentials. Each tenant's config lives in Voce's per-tenant Postgres store.
- Switching backends: Voce can use either Google Calendar OR Microsoft Calendar per tenant. The "Active calendar backend" dropdown picks which is in use. Both can be configured simultaneously and switched without a restart.
Related
- Google Calendar setup โ uses OAuth 2.0 refresh tokens via OAuth Playground. Different procedure, same dashboard fields.
- Booking windows format โ see dashboard field help text for the full syntax (DOW HH:MM-HH:MM, comma-separated, multiple windows per day allowed).
- Sales demo flow โ when a calendar backend is active, Voce automatically offers booking as a third option for sales-interested callers ("Connect now / Book demo / Callback").