Skip to main content

Node.js SDK

The Mobiscroll Connect Node.js SDK provides a convenient way to interact with the Mobiscroll Connect API from your Node.js applications.

Setup​

Install the package using your preferred package manager:

npm install @mobiscroll/connect-sdk

Client Initialization​

To use the SDK, you need to initialize the MobiscrollConnectClient with your client credentials.

Class: MobiscrollConnectClient

configMobiscrollConnectConfig

Configuration object.

clientIdstring

Your Client ID obtained from the Mobiscroll Connect dashboard.

clientSecretstring

Your Client Secret obtained from the Mobiscroll Connect dashboard.

redirectUristring

Your application's redirect URI that matches the one configured in the Mobiscroll Connect dashboard.

webhookPublicKeystring

Optional. A pinned whpk_ public key for webhooks.verifyWebhook. It is used only when the keys endpoint cannot be reached, and it stops verifying once Mobiscroll retires that key, so replace it on every key rotation. Leave it unset unless your handler cannot make outbound requests.

Usage:

import { MobiscrollConnectClient } from "@mobiscroll/connect-sdk";

const client = new MobiscrollConnectClient({
clientId: "YOUR_CLIENT_ID",
clientSecret: "YOUR_CLIENT_SECRET",
redirectUri: "YOUR_REDIRECT_URI",
});

Methods​

setCredentials​

Sets the access token for the client. This is required before making any API calls that require authentication (which is most of them).

Method: client.setCredentials(tokens)

tokensTokenResponse

The tokens object received from the auth.getToken method.

on​

Registers an event listener for client events.

Method: client.on(event, listener)

eventstring

The name of the event to listen for.

listenerfunction

The callback function to execute when the event is triggered.

getConfig​

Returns the current configuration of the client.

Method: client.getConfig()

Returns: MobiscrollConnectConfig

Token Refresh​

The Node.js SDK handles token refresh automatically. When any API call returns a 401 Unauthorized response and the client has a refresh_token stored, the SDK will silently exchange it for a new access token and retry the original request — with no action required from your application.

When the refresh succeeds, the SDK emits a tokens event with the updated TokenResponse. You must listen for this event and persist the new tokens, otherwise they will be lost when the process exits.

client.on('tokens', (updatedTokens) => {
// Persist updatedTokens in your database or session store
// so the new access_token and refresh_token survive future requests
});

If the refresh token itself is invalid or has been revoked, the SDK throws an AuthenticationError and the user must re-authorize.

Running more than one instance​

Concurrent calls on the same client that hit an expired token share one in-flight refresh. That covers one client object in one process. It does not reach across processes: separate containers, cluster workers or serverless invocations each refresh on their own, from the tokens they hold in memory.

Connect accepts several processes refreshing the same token at once, but a process that refreshes with a copy that is two or more refreshes out of date revokes the user's authorization — see Refreshing from several instances. So:

  • persist the tokens from the tokens event to storage that every instance reads, and
  • when a process starts a job or handles a request, call setCredentials with the current tokens from that storage instead of relying on the copy it loaded at startup.

Error Handling​

All SDK methods throw errors that extend MobiscrollConnectError. You can catch the base class or any specific subclass.

Error classHTTP statuserror.code
AuthenticationError401, 403AUTHENTICATION_ERROR
CalendarPermissionError403CALENDAR_PERMISSION_REQUIRED
ValidationError400, 422VALIDATION_ERROR
NotFoundError404NOT_FOUND_ERROR
RateLimitError429RATE_LIMIT_ERROR
ServerError5xxSERVER_ERROR
NetworkError—NETWORK_ERROR
WebhookVerificationError—WEBHOOK_VERIFICATION_ERROR

ValidationError exposes a details property with field-level validation errors. RateLimitError exposes retryAfter (seconds) and ServerError exposes status (the actual HTTP status code). WebhookVerificationError is thrown by webhooks.verifyWebhook and verifyWebhookSignature, not by API calls; its reason says why the delivery was rejected.

import {
AuthenticationError,
ValidationError,
RateLimitError,
MobiscrollConnectError,
} from '@mobiscroll/connect-sdk';

try {
const response = await client.events.list({ start: '2024-01-01' });
} catch (error) {
if (error instanceof AuthenticationError) {
// Token expired and refresh failed — re-authorize the user
} else if (error instanceof ValidationError) {
console.log(error.details);
} else if (error instanceof RateLimitError) {
console.log(`Retry after ${error.retryAfter}s`);
} else if (error instanceof MobiscrollConnectError) {
// Catch-all for any other SDK error
}
}

Auth API​

The client.auth resource handles the OAuth authorization flow, including generating authorization URLs, exchanging codes for tokens, and managing connection status. It corresponds to the /authorize, /token, and /connection-status endpoints.

Each account returned by getConnectionStatus reports grantedScopes and calendarPermissionGranted. A false flag means the account connected but withheld calendar access on the provider's consent screen, so it can list no calendars until the user reconnects — see Partial consent. Each account also reports syncState: reauth_required means the provider has since rejected the stored credentials and the account has stopped syncing — a different question from calendarPermissionGranted, which only records what was agreed at connect time. See Connection health.

When no connected account has calendar access, calendar and event calls raise CalendarPermissionError — accounts names the accounts that must reconnect. It subclasses AuthenticationError, so existing handlers keep working.

To localize the Connect pages, pass an optional lng to generateAuthUrl, e.g. generateAuthUrl({ userId, lng: 'es' }). When omitted, the UI falls back to the browser's Accept-Language header, then English; Arabic, Hebrew and Persian render right-to-left. See Supported languages for the languages Connect supports.


auth.generateAuthUrl​

Builds the URL that starts the authorization flow. Redirect the user to it: once they connect their calendars, Connect redirects them back to your redirect URI with an authorization code. The URL is built locally, without an API request. See GET /authorize.

Method: client.auth.generateAuthUrl(params)

Returns: string

userId is required: your identifier for the user. scope, state and lng are optional.

Usage:

// Generate the authorization URL
const authUrl = client.auth.generateAuthUrl({
userId: 'user-456',
// Optional parameters
// state: 'xyz789',
// scope: 'read-write',
// lng: 'es', // localize the Connect pages
});

// Redirect the user to authUrl
// res.redirect(authUrl);

auth.getToken​

Exchanges the authorization code from your redirect URI for an access token and a refresh token. The client stores the returned tokens and uses them for the calls that follow. See POST /token.

Method: client.auth.getToken(code)

Returns: Promise<TokenResponse>

codestring

The authorization code received on your redirect URI.

Usage:

// Exchange authorization code for access token
// The client uses the configured clientId and clientSecret
const tokenResponse = await client.auth.getToken(code);

// The client is automatically authenticated with the new token
// Persist tokenResponse to restore it later with client.setCredentials()

Calendars API​

The client.calendars resource allows you to list available calendars from all connected providers (Google, Outlook, etc.). It corresponds to the /calendars endpoints.


calendars.list​

Lists the calendars of every account the user has connected. See GET /calendars.

Method: client.calendars.list()

Returns: Promise<Calendar[]>

Usage:

const calendars = await client.calendars.list();

Events API​

The client.events resource provides methods to create, read, update, and delete calendar events across all connected accounts. It corresponds to the /events endpoints.


events.list​

Lists events from the user's calendars, one page at a time. When the response contains a nextPageToken, pass it to the next call to get the following page. See GET /events.

Method: client.events.list(params?)

Returns: Promise<EventsListResponse>

All fields are optional: start, end, calendarIds (calendar IDs grouped by provider), pageSize, nextPageToken and singleEvents.

Usage:

// Fetch initial events with date range filter
const response = await client.events.list({
pageSize: 50,
start: '2025-10-01T00:00:00Z',
end: '2025-10-31T23:59:59Z'
});
console.log(response.events);

// Load more events using nextPageToken
const nextResponse = await client.events.list({
pageSize: 50,
nextPageToken: response.nextPageToken
});

// Filter by specific calendars
const filteredEvents = await client.events.list({
pageSize: 25,
calendarIds: { google: ['personal@gmail.com', 'work@company.com'] }
});

// Get recurring event series masters (not expanded)
const masters = await client.events.list({
pageSize: 50,
singleEvents: false
});

events.create​

Creates an event in the given calendar. Set recurrence to create a recurring series. See POST /event.

Method: client.events.create(params)

Returns: Promise<EventResponse>

provider, calendarId, title, start and end are required. Optional fields include description, location, allDay and recurrence.

Usage:

// Create a simple event
const event = await client.events.create({
provider: 'google',
calendarId: 'primary',
title: 'Team Meeting',
description: 'Discuss project updates',
start: '2025-11-01T10:00:00Z',
end: '2025-11-01T11:00:00Z',
location: 'Conference Room A'
});

// Create a recurring event
const recurringEvent = await client.events.create({
provider: 'microsoft',
calendarId: 'AAMkAGVmMDEz...',
title: 'Weekly Standup',
start: '2025-11-01T09:00:00Z',
end: '2025-11-01T09:30:00Z',
allDay: false,
recurrence: {
frequency: 'WEEKLY',
interval: 1,
count: 10,
byDay: ['MO', 'WE', 'FR']
}
});

// Create an all-day event
const allDayEvent = await client.events.create({
provider: 'apple',
calendarId: 'https://caldav.icloud.com/.../calendars/...',
title: 'Conference',
start: '2025-11-15T00:00:00Z',
end: '2025-11-16T00:00:00Z',
allDay: true,
description: 'Annual tech conference'
});

events.update​

Updates an existing event. For an occurrence of a recurring series, set recurringEventId, and use updateMode to choose which occurrences change: this, following or all. See PUT /event.

Method: client.events.update(params)

Returns: Promise<EventResponse>

provider, calendarId and eventId are required. Pass only the event fields you want to change, plus recurringEventId and updateMode for recurring events.

Usage:

// Update a simple event
const updatedEvent = await client.events.update({
provider: 'google',
calendarId: 'primary',
eventId: 'event123abc',
title: 'Updated Team Meeting',
start: '2025-11-01T14:00:00Z',
end: '2025-11-01T15:00:00Z'
});

// Update a single instance of a recurring event
const updatedInstance = await client.events.update({
provider: 'microsoft',
eventId: 'instance456',
recurringEventId: 'series123',
calendarId: 'AAMkAGVmMDEz...',
updateMode: 'this',
title: 'Standup - Special Topic Today',
start: '2025-11-01T09:00:00Z',
end: '2025-11-01T10:00:00Z'
});

// Update all future occurrences
const updatedFuture = await client.events.update({
provider: 'apple',
eventId: 'recurring-event-id',
calendarId: 'https://caldav.icloud.com/.../calendars/...',
updateMode: 'following',
location: 'New Conference Room B'
});

events.delete​

Deletes an event. For an occurrence of a recurring series, set recurringEventId, and use deleteMode to choose which occurrences are deleted: this, following or all. See DELETE /event.

Method: client.events.delete(params)

Returns: Promise<void>

provider, calendarId and eventId are required. recurringEventId and deleteMode are optional.

Usage:

// Delete a simple event
await client.events.delete({
provider: 'google',
calendarId: 'primary',
eventId: 'event123abc'
});

// Delete a single instance of a recurring event
await client.events.delete({
provider: 'microsoft',
calendarId: 'AAMkAGVmMDEz...',
eventId: 'instance456',
recurringEventId: 'series123',
deleteMode: 'this'
});

// Delete entire recurring series
await client.events.delete({
provider: 'apple',
calendarId: 'https://caldav.icloud.com/.../calendars/...',
eventId: 'recurring-event-id',
deleteMode: 'all'
});

Webhooks API​

The client.webhooks resource lets you subscribe a calendar to change notifications, unsubscribe an existing channel, and verify the deliveries Connect sends to your webhook URL. Subscribing and unsubscribing correspond to the /subscribe-webhook and /unsubscribe-webhook endpoints. Connect subscribes and renews channels automatically when a calendar is connected — those two methods are for managing a specific channel explicitly.


webhooks.subscribeWebhook​

Subscribes a calendar to change notifications. See POST /subscribe-webhook.

Method: client.webhooks.subscribeWebhook(params)

Returns: Promise<SubscribeWebhookResponse>

provider and calendarId are required. channelId is optional and generated by the server when omitted. expiration is an optional Unix timestamp in milliseconds.

Usage:

const subscription = await client.webhooks.subscribeWebhook({
provider: 'google',
calendarId: 'work@company.com',
channelId: 'my-channel-123'
});

webhooks.unsubscribeWebhook​

Removes an existing webhook subscription. See POST /unsubscribe-webhook.

Method: client.webhooks.unsubscribeWebhook(params)

Returns: Promise<UnsubscribeWebhookResponse>

provider and channelId are required. resourceId is the provider resource ID returned by subscribeWebhook, where the provider uses one.

Usage:

await client.webhooks.unsubscribeWebhook({
provider: 'google',
channelId: 'my-channel-123',
resourceId: 'resource-abc'
});

webhooks.verifyWebhook​

Checks that a delivery to your webhook URL came from Mobiscroll Connect and was not changed on the way, then returns it parsed. It verifies the Ed25519 signature and rejects a webhook-timestamp more than 5 minutes from your server's clock — see Verifying deliveries. Available from version 1.6.0.

Method: client.webhooks.verifyWebhook(payload, headers)

Returns: Promise<WebhookDelivery>

Throws: WebhookVerificationError when the delivery is not genuine. Respond with a 4xx and do not process it.

payloadstring | Buffer

The raw request body, exactly as received. Do not pass a parsed object: a JSON body parser that runs first changes the bytes, and every signature check fails.

headersIncomingHttpHeaders | Headers

The request headers, in any casing. Must include webhook-id, webhook-timestamp and webhook-signature.

The SDK fetches the public keys from /.well-known/webhook-keys on the first delivery, not when the client is created, and caches them for the whole process, shared by every client instance. It refreshes them as the endpoint's Cache-Control header allows. When no signature matches, it fetches the keys again before rejecting the delivery, at most once a minute, so a key rotation needs no action from you. If a refresh fails, it keeps using the keys it already has. The pinned webhookPublicKey is used only when no keys could be fetched at all.

error.reason is one of:

reasonMeaning
missing_headerswebhook-id, webhook-timestamp or webhook-signature is missing
invalid_timestampwebhook-timestamp is not a Unix time in seconds
timestamp_out_of_tolerancewebhook-timestamp is more than 5 minutes from your server's clock
no_public_keysNo keys could be fetched and no webhookPublicKey is configured
no_matching_signatureNo signature matches any key — a forged or modified delivery
invalid_payloadThe body is a parsed object, not raw, or is not a JSON object

Usage:

import express from 'express';
import { WebhookVerificationError } from '@mobiscroll/connect-sdk';

const app = express();

// Register before any app-wide express.json(), which would consume the raw body.
app.post('/webhooks/mobiscroll', express.raw({ type: 'application/json' }), async (req, res) => {
let delivery;
try {
delivery = await client.webhooks.verifyWebhook(req.body, req.headers);
} catch (error) {
if (error instanceof WebhookVerificationError) {
// 503 lets Connect retry when the keys could not be loaded; 401 is final.
return res.sendStatus(error.reason === 'no_public_keys' ? 503 : 401);
}
throw error;
}
res.sendStatus(204);
handleDelivery(delivery);
});

verifyWebhookSignature​

Verifies a delivery against public keys you supply, with no network request. Use it in handlers that cannot make outbound requests; otherwise prefer webhooks.verifyWebhook, which keeps the keys current for you. Available from version 1.6.0.

Function: verifyWebhookSignature(payload, headers, publicKeys, options?)

Returns: void

Throws: WebhookVerificationError, with the same reason values as webhooks.verifyWebhook.

payloadstring | Buffer

The raw request body, exactly as received.

headersIncomingHttpHeaders | Headers

The request headers, in any casing.

publicKeysstring[]

whpk_ public keys. The delivery is genuine if any signature verifies against any of them.

optionsVerifyWebhookSignatureOptions

Optional. toleranceSeconds sets the allowed webhook-timestamp age in either direction (default 300); now overrides the current Unix time in seconds.

Usage:

import { verifyWebhookSignature } from '@mobiscroll/connect-sdk';

verifyWebhookSignature(req.body, req.headers, [process.env.MOBISCROLL_WEBHOOK_PUBLIC_KEY!]);
const delivery = JSON.parse(req.body.toString('utf8'));

A pinned key stops verifying when Mobiscroll retires it, so you must replace it on every key rotation.