Webhooks API
Use webhooks to receive near real-time event change notifications from connected calendar providers.
When a user modifies an event in Google, Microsoft, Apple, or CalDAV, Connect can forward the normalized change payload to your configured project webhook URL after you subscribe the calendar via POST /subscribe-webhook.
This API lets you:
- Subscribe a calendar to provider notifications
- Unsubscribe an existing webhook channel
- Receive normalized webhook payloads at your application webhook URL
Supported providers
- Google Calendar (
google) - Microsoft Outlook (
microsoft) - Apple Calendar (
apple) - CalDAV (
caldav)
Subscribe webhook
Creates a webhook subscription for a specific calendar.
Endpoint: POST /subscribe-webhook
Authentication
Requires Bearer token authentication:
Authorization: Bearer YOUR_ACCESS_TOKEN
Request parameters
Provider name. Supported values: google, microsoft, apple, caldav.
Calendar ID to subscribe.
Optional custom subscription channel ID.
Optional Unix timestamp in milliseconds. Provider-specific subscription expiration.
Response
true when subscription is created.
Provider associated with this subscription.
Provider subscription details.
Unique webhook channel/subscription identifier.
Provider resource identifier when available.
ISO 8601 expiration timestamp when available.
Provider callback URL used by the subscription.
Mobiscroll Connect callback endpoint registered with the provider.
Channel ID used for the subscription.
Error responses
- 400 - Missing required parameters, unsupported provider, or project webhook URL not configured
- 401 - Unauthorized (invalid or missing Bearer token)
- 500 - Internal server error
Example
- REST
- Node.js SDK
- Python SDK
- PHP SDK
- .NET SDK
- Java SDK
- Go SDK
- Ruby SDK
curl -X POST "https://connect.mobiscroll.com/api/subscribe-webhook" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"provider": "google",
"calendarId": "work@company.com",
"channelId": "my-channel-123"
}'
const subscription = await client.webhooks.subscribeWebhook({
provider: 'google',
calendarId: 'work@company.com',
channelId: 'my-channel-123'
});
subscription = client.webhooks.subscribe_webhook(
'google',
'work@company.com',
channel_id='my-channel-123'
)
$subscription = $client->webhooks()->subscribeWebhook([
'provider' => 'google',
'calendarId' => 'work@company.com',
'channelId' => 'my-channel-123',
]);
var subscription = await client.Webhooks.SubscribeWebhookAsync(new WebhookSubscribeData
{
Provider = "google",
CalendarId = "work@company.com",
ChannelId = "my-channel-123",
});
import com.mobiscroll.connect.Provider;
import com.mobiscroll.connect.models.WebhookSubscribeParams;
import com.mobiscroll.connect.models.WebhookSubscribeResponse;
WebhookSubscribeResponse subscription = client.webhooks().subscribeWebhook(
WebhookSubscribeParams.builder()
.provider(Provider.GOOGLE)
.calendarId("work@company.com")
.channelId("my-channel-123")
.build());
import mobiscroll "github.com/acidb/mobiscroll-connect-sdks/sdks/go"
subscription, err := client.Webhooks().SubscribeWebhook(ctx, &mobiscroll.SubscribeWebhookParams{
Provider: mobiscroll.ProviderGoogle,
CalendarID: "work@company.com",
ChannelID: "my-channel-123",
})
subscription = client.webhooks.subscribe_webhook(
provider: Mobiscroll::Connect::Provider::GOOGLE,
calendar_id: 'work@company.com',
channel_id: 'my-channel-123'
)
{
"success": true,
"provider": "google",
"subscription": {
"channelId": "my-channel-123",
"resourceId": "resource-abc",
"expiration": "2026-03-19T10:00:00.000Z",
"webhookUrl": "https://your-connect-server/api/webhook-receiver/google"
},
"serverWebhookUrl": "https://your-connect-server/api/webhook-receiver/google",
"channelId": "my-channel-123"
}
Unsubscribe webhook
Removes an existing webhook subscription channel.
Endpoint: POST /unsubscribe-webhook
Authentication
Requires Bearer token authentication.
Request parameters
Provider name. Supported values: google, microsoft, apple, caldav.
Channel/subscription ID to remove.
Optional provider resource ID when applicable.
undefinedResponse
true when request is accepted and local mapping cleanup is completed.
Additional status detail.
Error responses
- 400 - Missing parameters or unsupported provider
- 401 - Unauthorized (invalid or missing Bearer token)
- 500 - Internal server error
Example
- REST
- Node.js SDK
- Python SDK
- PHP SDK
- .NET SDK
- Java SDK
- Go SDK
- Ruby SDK
curl -X POST "https://connect.mobiscroll.com/api/unsubscribe-webhook" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"provider": "google",
"channelId": "my-channel-123",
"resourceId": "resource-abc"
}'
await client.webhooks.unsubscribeWebhook({
provider: 'google',
channelId: 'my-channel-123',
resourceId: 'resource-abc'
});
client.webhooks.unsubscribe_webhook(
'google',
'my-channel-123',
resource_id='resource-abc'
)
$client->webhooks()->unsubscribeWebhook([
'provider' => 'google',
'channelId' => 'my-channel-123',
'resourceId' => 'resource-abc',
]);
await client.Webhooks.UnsubscribeWebhookAsync(new WebhookUnsubscribeData
{
Provider = "google",
ChannelId = "my-channel-123",
ResourceId = "resource-abc",
});
import com.mobiscroll.connect.Provider;
import com.mobiscroll.connect.models.WebhookUnsubscribeParams;
client.webhooks().unsubscribeWebhook(
WebhookUnsubscribeParams.builder()
.provider(Provider.GOOGLE)
.channelId("my-channel-123")
.resourceId("resource-abc")
.build());
import mobiscroll "github.com/acidb/mobiscroll-connect-sdks/sdks/go"
_, err := client.Webhooks().UnsubscribeWebhook(ctx, &mobiscroll.UnsubscribeWebhookParams{
Provider: mobiscroll.ProviderGoogle,
ChannelID: "my-channel-123",
ResourceID: "resource-abc",
})
client.webhooks.unsubscribe_webhook(
provider: Mobiscroll::Connect::Provider::GOOGLE,
channel_id: 'my-channel-123',
resource_id: 'resource-abc'
)
{
"success": true,
"message": "Webhook unsubscribed successfully"
}
Client webhook payload
When a provider notification is processed, Mobiscroll Connect forwards a normalized payload to your configured project webhook URL.
Notification source provider.
User ID in your system.
Calendar ID where changes were detected.
Changed events list.
Calendar ID where the event belongs.
Event ID.
Event provider.
Event title.
Event description/notes (optional).
ISO 8601 timestamp of the last provider-side modification (optional).
Event start date/time.
Event end date/time.
Indicates all-day event.
Recurring series master ID when this event is an instance (optional).
One of created, updated, deleted.
Optional event color.
Optional event location.
Optional attendee list.
Attendee email.
Response status: accepted, declined, tentative, or none.
Indicates if attendee is organizer. For Google, the organizer appears in this list only when Google lists them as a guest — see attendees in the events reference.
Optional custom key-value pairs.
Optional conference metadata.
Conference meeting URL.
If true, provider may auto-generate an online meeting link.
Conference provider identifier.
Provider-specific conference payload.
Optional availability: busy or free.
Optional privacy: public, private, or confidential.
Optional event status: confirmed, tentative, or cancelled.
Optional provider event link.
Provider-native event object.
Overall change summary: created, updated, deleted, or mixed.
ISO 8601 processing timestamp.
Additional webhook metadata.
Subscription channel ID.
Number of events in this delivery.
true only for a provider initial-sync delivery (when a channel is first established). Ordinary changes are always false, even large ones such as editing many occurrences of a recurring series.
Example delivery
{
"provider": "google",
"userId": "user-123",
"calendarId": "work@company.com",
"events": [
{
"id": "event-abc",
"provider": "google",
"calendarId": "work@company.com",
"title": "Product review",
"description": "Quarterly review",
"lastModified": "2026-03-10T09:00:00.000Z",
"start": "2026-03-12T09:00:00.000Z",
"end": "2026-03-12T10:00:00.000Z",
"allDay": false,
"recurringEventId": "series-master-id",
"color": "#9fc6e7",
"location": "Office / Meeting room",
"attendees": [
{
"email": "user@example.com",
"status": "accepted",
"organizer": true
}
],
"custom": {
"yourCustomKey": "yourCustomValue"
},
"conference": {
"url": "https://meet.example.com/abc",
"provider": "google-meet"
},
"availability": "busy",
"privacy": "private",
"status": "confirmed",
"link": "https://provider-event-link",
"changeType": "updated",
"original": {}
}
],
"changeType": "updated",
"timestamp": "2026-03-12T09:01:12.000Z",
"metadata": {
"channelId": "sub-123",
"eventCount": 1,
"isInitialSync": false
}
}
Subscription lifecycle
You do not manage provider subscriptions. Connect subscribes a user's calendars when they connect an account, and renews those subscriptions automatically before the provider's channel expires. Google push channels last about a week and Microsoft subscriptions slightly less, but neither lifetime is something your integration has to track.
POST /subscribe-webhook remains available for subscribing a specific calendar explicitly, and is safe to call repeatedly — it replaces any existing subscription for that calendar.
When a connection breaks
Renewal cannot fix every failure. If the end user revokes your app in their Google account, an administrator withdraws consent, or the credentials otherwise stop working, no server-side action can restore the connection — providers only issue calendar credentials at consent time.
When that happens, Connect sends a connection event to your webhook URL:
{
"type": "connection.reauth_required",
"provider": "google",
"account": "user@gmail.com",
"userId": "user-123",
"reason": "invalid_grant",
"calendarIds": ["work@company.com", "personal@gmail.com"],
"timestamp": "2026-09-03T09:14:02.000Z"
}
connection.reauth_required — the account's calendars have stopped syncing and the user must reconnect.
Provider the affected account belongs to.
The provider account, usually the user's email address.
User ID in your system.
Machine-readable cause, for example invalid_grant.
Calendars affected. Supporting detail — the subject of the event is the account, not any one calendar.
type before reading the payloadA connection event has no events array. Calendar change deliveries and connection events arrive at the same URL, so check type first.
To recover, send that user through GET /api/oauth/authorize again. Completing the flow restores the connection and re-subscribes every calendar on the account — there is no follow-up call to make. One event covers the whole account, so one reconnect resolves it.
You can also poll for the same state: every account in GET /api/oauth/connection-status carries syncState, which reads reauth_required for exactly these accounts. Use the event to react immediately and the endpoint to reconcile after your own downtime.
Setup requirements
- Configure Webhook URL in your Connect application settings. See Application setup.
- Ensure your webhook endpoint is public, reachable, and returns
2xxquickly. - Keep endpoint handling idempotent and tolerant of out-of-order notifications.
Operational notes
- Delivery forwarding to your webhook URL is best-effort and should be handled with idempotent processing on your side.
- Connect waits for your response before acknowledging the provider, and gives up after 10 seconds. Return
2xximmediately and process asynchronously — a slow endpoint delays our acknowledgement to Google or Microsoft, which over time can cost the subscription. - Provider-side notifications may be emitted for changes regardless of where the change originated.
- A single change can produce more than one provider notification — Microsoft may write the event several times for one user action, and providers deliver at least once — so expect the same event to arrive more than once.
- Changes you make through the Connect API do not come back to you as deliveries. Every provider notification caused by your own
POST /event,PUT /eventorDELETE /eventcall is filtered for 30 seconds after the write (short-lived in-memory dedup window), including notifications that arrive before the API call returns. - In distributed or multi-instance setups, add an application-level origin marker in
custom(for examplecustom.source = "my-system") and ignore matching webhook events as an additional loop-prevention safeguard. - For Apple, event change detection is based on periodic synchronization (polled every 5 minutes) rather than provider-native push.
- For CalDAV, event change detection is based on periodic synchronization rather than provider-native push.
- A single delivery may contain multiple event changes and return
changeType: "mixed". - When notifications are filtered out, no events may be delivered in that callback cycle.