Outlook Calendar Integration
Set up and use the Microsoft 365 / Outlook Calendar integration for bidirectional sync between Event Schedule and Outlook through Microsoft Graph.
Prerequisites
- A Microsoft Entra ID (Azure AD) tenant or an Azure account to register an application
- Access to the Azure Portal to create an app registration
- The application deployed at a public HTTPS URL (needed for the OAuth redirect and for Microsoft Graph webhook change-notifications)
Installs without a public HTTPS URL still work. Instead of near-real-time webhooks, inbound changes are picked up by the 15-minute microsoft:sync polling fallback.
For near-real-time webhooks, run an asynchronous queue (set QUEUE_CONNECTION=database and keep php artisan queue:work running). Inbound sync is dispatched to the queue so Microsoft Graph gets a fast response. On the default sync connection the sync runs inside the webhook request, which can be slow enough that Graph deprovisions the subscription. Without a worker, inbound changes still arrive via the 15-minute poll.
Setup Instructions
1. Azure App Registration
- Go to the Azure Portal and open Microsoft Entra ID > App registrations > New registration
- Enter a name for the application (for example, "Event Schedule")
- Under Supported account types, choose "Accounts in any organizational directory and personal Microsoft accounts" (this matches
MICROSOFT_TENANT=common) - Under Redirect URI, select the Web platform and enter:
{APP_URL}/microsoft-calendar/callback - Click Register
2. API Permissions
- In the app registration, open API permissions > Add a permission > Microsoft Graph > Delegated permissions
- Add the following delegated permissions:
Calendars.ReadWriteoffline_accessopenidemailprofile
- If your tenant requires it, grant admin consent for the permissions
3. Client Secret and Client ID
- Open Certificates & secrets > New client secret, then copy the secret Value immediately (it is only shown once)
- Open the Overview page and copy the Application (client) ID
4. Environment Configuration
Add the following environment variables to your .env file:
MICROSOFT_CLIENT_ID=your_application_client_id
MICROSOFT_CLIENT_SECRET=your_client_secret_value
MICROSOFT_REDIRECT_URI=https://your-domain.com/microsoft-calendar/callback
MICROSOFT_TENANT=common
MICROSOFT_WEBHOOK_SECRET=a_long_random_string
Variable Reference
| Variable | Description |
|---|---|
MICROSOFT_CLIENT_ID |
The Application (client) ID from the app registration Overview page |
MICROSOFT_CLIENT_SECRET |
The client secret Value created under Certificates & secrets |
MICROSOFT_REDIRECT_URI |
Must exactly match the redirect URI registered in Azure ({APP_URL}/microsoft-calendar/callback) |
MICROSOFT_TENANT |
Use common for multi-tenant plus personal accounts, or your specific tenant id for a single-tenant app |
MICROSOFT_WEBHOOK_SECRET |
Required for near-real-time webhooks (public HTTPS URL); not needed for polling-only installs. Any long random string. It is the clientState that authenticates inbound Microsoft Graph notifications |
Set MICROSOFT_WEBHOOK_SECRET to a long random value. Microsoft Graph echoes it back as the clientState on every change notification, and Event Schedule rejects any notification whose value does not match.
Features
User Features
- Connect Outlook Calendar: Each user connects their Microsoft account through OAuth from Settings > Outlook Calendar
- Per-Schedule Sync Direction: Choose To Outlook, From Outlook, or Both on the schedule's Integrations > Outlook Calendar tab
- Two-Way Sync: Events stay in sync between Event Schedule and Outlook
- Teams Meeting Links: Optionally add a Microsoft Teams join link to online events through a per-schedule toggle
- Near-Real-Time Inbound: Microsoft Graph subscriptions push Outlook changes back to Event Schedule
- Polling Fallback: A 15-minute
microsoft:synccommand catches anything webhooks miss - Subscription Renewal: A daily
microsoft:refresh-webhookscommand renews Graph subscriptions, which expire about every 2.5 days
Event Information Synced
- Event name
- Event description (with venue and URL information)
- Start and end times
- Location (venue address)
- Microsoft Teams join link (for online events when enabled)
Sync Direction
Each schedule can choose one of three sync directions:
| Direction | Description |
|---|---|
| To Outlook | Event Schedule events are pushed to the connected Outlook calendar |
| From Outlook | Outlook events are imported into Event Schedule |
| Both | Changes flow in both directions and stay in sync |
Microsoft Teams Meeting Links
When the Teams toggle is enabled for a schedule, events without a physical venue (online events) get a Microsoft Teams meeting created through Graph, and the join link is written back to the event.
Real-Time Sync and Polling Fallback
- Microsoft Graph subscriptions deliver change notifications to the webhook endpoint for near-real-time inbound sync
- The 15-minute
microsoft:synccommand polls for changes as a fallback, and is the primary path when no public URL is available - The daily
microsoft:refresh-webhookscommand renews subscriptions before they expire (about every 2.5 days)
Usage
For Users
1. Connect Outlook Calendar
- Go to your settings page (
/settings) - Find the "Outlook Calendar" section
- Click "Connect Outlook Calendar"
- Authorize the application in the Microsoft sign-in flow
2. Choose a Calendar and Sync Direction
- Open the schedule's Integrations tab
- Select the "Outlook Calendar" tab
- Pick which Outlook calendar to sync with
- Choose the sync direction: To Outlook, From Outlook, or Both
3. Push Events to Outlook
- Create or publish events as usual
- While connected, events are pushed to the selected Outlook calendar automatically
4. Enable Teams Meeting Links
- On the schedule's Outlook Calendar tab, enable the Microsoft Teams toggle
- Online (venue-less) events now get a Teams join link written to the event
Automatic Sync
Once a schedule is connected, events are synced automatically when:
- Created (if the user has Outlook Calendar connected)
- Updated (if the user has Outlook Calendar connected)
- Deleted (if the event was previously synced)
Inbound polling and subscription renewal run through scheduled commands (microsoft:sync every 15 minutes and microsoft:refresh-webhooks daily). Make sure the Laravel scheduler cron is active: * * * * * php artisan schedule:run
API Endpoints
| Endpoint | Description |
|---|---|
GET /microsoft-calendar/redirect |
Start OAuth flow |
GET /microsoft-calendar/callback |
OAuth callback |
GET /microsoft-calendar/reauthorize |
Re-run consent to obtain a refresh token |
GET /microsoft-calendar/disconnect |
Disconnect Outlook Calendar |
GET /microsoft-calendar/calendars |
Get user's calendars |
POST /microsoft-calendar/sync/{subdomain} |
Sync a schedule's events |
POST /microsoft-calendar/sync-event/{subdomain}/{eventId} |
Sync a specific event |
DELETE /microsoft-calendar/unsync-event/{subdomain}/{eventId} |
Remove event from Outlook |
GET /microsoft-calendar/webhook |
Microsoft Graph subscription validation handshake |
POST /microsoft-calendar/webhook |
Microsoft Graph change notifications (also handles the validation handshake) |
Scheduled Commands
These Artisan commands keep inbound sync and Graph subscriptions healthy:
| Command | Frequency | Description |
|---|---|---|
microsoft:sync |
Every 15 minutes | Polls Outlook for changes (inbound sync fallback) |
microsoft:refresh-webhooks |
Daily | Renews Microsoft Graph subscriptions before they expire |
These commands run through the Laravel scheduler, which requires the following cron entry:
* * * * * php artisan schedule:run
Troubleshooting
Common Issues
No refresh token / repeated re-authentication
- Disconnect and reconnect the account
- Make sure the
offline_accessscope is granted in the app registration
Teams meeting link not created
- Personal Microsoft accounts may not support Teams for Business meetings
- In that case the app falls back to creating a normal event without a Teams link
Inbound changes not updating
- Confirm the app has a public HTTPS URL so Graph can reach the webhook endpoint
- Without a public URL, rely on the 15-minute
microsoft:syncpoll and confirm the scheduler cron is running
Subscription creation fails
- Ensure
MICROSOFT_WEBHOOK_SECRETis set - Ensure the notification URL is publicly reachable over HTTPS
Logs
Sync operations are logged in the application logs. Check storage/logs/laravel.log for detailed information about sync operations.
Security Considerations
- Token Storage: Microsoft OAuth tokens are stored encrypted at rest, per user
- Rotating Refresh Tokens: Microsoft rotates the refresh token on each refresh, and the app stores the latest one
- Webhook Authentication:
MICROSOFT_WEBHOOK_SECRET(theclientState) authenticates inbound Graph notifications, and mismatched notifications are rejected - Secrets in .env: Keep the client secret and webhook secret in
.env, and never commit them to source control