Skip to main content

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

  1. A Microsoft Entra ID (Azure AD) tenant or an Azure account to register an application
  2. Access to the Azure Portal to create an app registration
  3. The application deployed at a public HTTPS URL (needed for the OAuth redirect and for Microsoft Graph webhook change-notifications)
No public URL?

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.

Queue worker required for webhooks

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

  1. Go to the Azure Portal and open Microsoft Entra ID > App registrations > New registration
  2. Enter a name for the application (for example, "Event Schedule")
  3. Under Supported account types, choose "Accounts in any organizational directory and personal Microsoft accounts" (this matches MICROSOFT_TENANT=common)
  4. Under Redirect URI, select the Web platform and enter: {APP_URL}/microsoft-calendar/callback
  5. Click Register

2. API Permissions

  1. In the app registration, open API permissions > Add a permission > Microsoft Graph > Delegated permissions
  2. Add the following delegated permissions:
    • Calendars.ReadWrite
    • offline_access
    • openid
    • email
    • profile
  3. If your tenant requires it, grant admin consent for the permissions

3. Client Secret and Client ID

  1. Open Certificates & secrets > New client secret, then copy the secret Value immediately (it is only shown once)
  2. Open the Overview page and copy the Application (client) ID

4. Environment Configuration

Add the following environment variables to your .env file:

.env
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
Webhook secret is required

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

  1. Connect Outlook Calendar: Each user connects their Microsoft account through OAuth from Settings > Outlook Calendar
  2. Per-Schedule Sync Direction: Choose To Outlook, From Outlook, or Both on the schedule's Integrations > Outlook Calendar tab
  3. Two-Way Sync: Events stay in sync between Event Schedule and Outlook
  4. Teams Meeting Links: Optionally add a Microsoft Teams join link to online events through a per-schedule toggle
  5. Near-Real-Time Inbound: Microsoft Graph subscriptions push Outlook changes back to Event Schedule
  6. Polling Fallback: A 15-minute microsoft:sync command catches anything webhooks miss
  7. Subscription Renewal: A daily microsoft:refresh-webhooks command 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:sync command polls for changes as a fallback, and is the primary path when no public URL is available
  • The daily microsoft:refresh-webhooks command renews subscriptions before they expire (about every 2.5 days)

Usage

For Users

1. Connect Outlook Calendar

  1. Go to your settings page (/settings)
  2. Find the "Outlook Calendar" section
  3. Click "Connect Outlook Calendar"
  4. Authorize the application in the Microsoft sign-in flow

2. Choose a Calendar and Sync Direction

  1. Open the schedule's Integrations tab
  2. Select the "Outlook Calendar" tab
  3. Pick which Outlook calendar to sync with
  4. Choose the sync direction: To Outlook, From Outlook, or Both

3. Push Events to Outlook

  1. Create or publish events as usual
  2. While connected, events are pushed to the selected Outlook calendar automatically

4. Enable Teams Meeting Links

  1. On the schedule's Outlook Calendar tab, enable the Microsoft Teams toggle
  2. 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)
Scheduled Sync

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:

crontab
* * * * * php artisan schedule:run

Troubleshooting

Common Issues

No refresh token / repeated re-authentication

  • Disconnect and reconnect the account
  • Make sure the offline_access scope 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:sync poll and confirm the scheduler cron is running

Subscription creation fails

  • Ensure MICROSOFT_WEBHOOK_SECRET is 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

  1. Token Storage: Microsoft OAuth tokens are stored encrypted at rest, per user
  2. Rotating Refresh Tokens: Microsoft rotates the refresh token on each refresh, and the app stores the latest one
  3. Webhook Authentication: MICROSOFT_WEBHOOK_SECRET (the clientState) authenticates inbound Graph notifications, and mismatched notifications are rejected
  4. Secrets in .env: Keep the client secret and webhook secret in .env, and never commit them to source control