BridgeKit Documentation

A Laravel library for third-party integrations — OAuth providers, credential storage (S3, FTP, SFTP), SharePoint, webhooks, and a single FileStorageInterface including recursive listTree().

Installation

Require the package via Composer:

composer require bridgekit-tools/bridgekit-lib

The service provider and facade are auto-discovered. To publish the configuration file:

php artisan vendor:publish --tag=bridgekit-config

Configuration

Add your provider credentials to .env:

# Google
BRIDGEKIT_GOOGLE_CLIENT_ID=your-client-id
BRIDGEKIT_GOOGLE_CLIENT_SECRET=your-client-secret
BRIDGEKIT_GOOGLE_REDIRECT_URI=https://app.test/callback/google

# Microsoft
BRIDGEKIT_MICROSOFT_CLIENT_ID=
BRIDGEKIT_MICROSOFT_CLIENT_SECRET=
BRIDGEKIT_MICROSOFT_REDIRECT_URI=
BRIDGEKIT_MICROSOFT_TENANT=common

# SharePoint (Microsoft Graph — site library; optional if you only use OneDrive)
BRIDGEKIT_SHAREPOINT_SITE_ID=
# Or resolve by path, e.g. /contoso.sharepoint.com:/sites/marketing
BRIDGEKIT_SHAREPOINT_SITE_PATH=
BRIDGEKIT_SHAREPOINT_DRIVE_ID=

# Meta (Facebook)
BRIDGEKIT_META_CLIENT_ID=
BRIDGEKIT_META_CLIENT_SECRET=
BRIDGEKIT_META_REDIRECT_URI=
BRIDGEKIT_META_GRAPH_VERSION=v21.0

# LinkedIn
BRIDGEKIT_LINKEDIN_CLIENT_ID=
BRIDGEKIT_LINKEDIN_CLIENT_SECRET=
BRIDGEKIT_LINKEDIN_REDIRECT_URI=

# X (Twitter)
BRIDGEKIT_X_CLIENT_ID=
BRIDGEKIT_X_CLIENT_SECRET=
BRIDGEKIT_X_REDIRECT_URI=

# Dropbox
BRIDGEKIT_DROPBOX_CLIENT_ID=
BRIDGEKIT_DROPBOX_CLIENT_SECRET=
BRIDGEKIT_DROPBOX_REDIRECT_URI=

# FTP
BRIDGEKIT_FTP_HOST=ftp.example.com
BRIDGEKIT_FTP_PORT=21
BRIDGEKIT_FTP_USERNAME=
BRIDGEKIT_FTP_PASSWORD=
BRIDGEKIT_FTP_SSL=false
BRIDGEKIT_FTP_PASSIVE=true
BRIDGEKIT_FTP_ROOT=/

# S3 (AWS or S3-compatible: MinIO, DigitalOcean Spaces, etc.)
BRIDGEKIT_S3_KEY=
BRIDGEKIT_S3_SECRET=
BRIDGEKIT_S3_REGION=us-east-1
BRIDGEKIT_S3_BUCKET=my-bucket
BRIDGEKIT_S3_ENDPOINT=

# SFTP
BRIDGEKIT_SFTP_HOST=sftp.example.com
BRIDGEKIT_SFTP_PORT=22
BRIDGEKIT_SFTP_USERNAME=
BRIDGEKIT_SFTP_PASSWORD=
BRIDGEKIT_SFTP_PRIVATE_KEY=
BRIDGEKIT_SFTP_ROOT=/

The config file at config/bridgekit.php maps each of these to its provider section.

Quick Start

Here's a minimal example — authenticate a user via Google OAuth then list their Drive files:

use BridgeKit\Facades\BridgeKit;

// 1. Redirect to Google consent screen
$url = BridgeKit::google()
    ->auth()
    ->getAuthorizationUrl(['https://www.googleapis.com/auth/drive.readonly']);

return redirect($url);

// 2. Handle callback
$token = BridgeKit::google()
    ->auth()
    ->handleCallback($request->code);

// 3. Use the token
$files = BridgeKit::google()
    ->setToken($token)
    ->drive()
    ->listFiles();

Architecture

BridgeKit follows a layered architecture:

ConnectManager                → resolves providers by name
  ├─ AbstractProvider          → OAuth providers (token + config)
  │    └─ Services             → Drive, Gmail, Calendar, Posts...
  └─ AbstractStorageProvider   → credential-based providers (FTP, S3, SFTP)
       └─ StorageService       → implements FileStorageInterface

Contracts/                     → interfaces each service implements
DTOs/                          → readonly typed value objects
Enums/                         → Provider, Visibility, MediaType, etc.

Key design decisions

  • Two provider base classesAbstractProvider for OAuth flows, AbstractStorageProvider for credential-based storage
  • Services are lazy singletons — resolved on first access, cached until token changes
  • DTOs are immutablefinal readonly class with JsonSerializable
  • Unified FileStorageInterface — same API across Google Drive, OneDrive, SharePoint, Dropbox, FTP, S3, SFTP, including listTree() for recursive folder trees
  • S3 uses native AWS Signature V4 — zero external dependencies, works with any S3-compatible endpoint

Providers

Each provider is an entry point that holds the OAuth token and exposes its services:

use BridgeKit\Facades\BridgeKit;
use BridgeKit\Enums\Provider;

// Via the facade
$google = BridgeKit::google();
$microsoft = BridgeKit::microsoft();

// Via the enum
$provider = BridgeKit::provider(Provider::LinkedIn);

// With custom config (bypasses config/bridgekit.php)
$custom = BridgeKit::google([
    'client_id' => 'custom-id',
    'client_secret' => 'custom-secret',
]);

Setting the token

Every service call requires an authenticated token. Call setToken() once on the provider:

$google = BridgeKit::google()->setToken($token);

// All services share the same token
$google->drive()->listFiles();
$google->gmail()->send($email);
$google->calendar()->listEvents();

When you call setToken(), the service cache is flushed — new service instances will use the new token.

Available services per provider

ProviderAuthServicesContracts
GoogleOAuth 2.0drive(), gmail(), calendar(), webhooks()FileStorage, EmailSender, Calendar, Webhook
MicrosoftOAuth 2.0onedrive(), sharepoint(), outlook(), calendar(), webhooks()FileStorage (×2), EmailSender, Calendar, Webhook
MetaOAuth 2.0posts(), webhooks()PostPublisher, Webhook
LinkedInOAuth 2.0posts()PostPublisher
XOAuth 2.0posts(), webhooks()PostPublisher, Webhook
DropboxOAuth 2.0storage()FileStorage
FTPCredentialsstorage()FileStorage
S3Credentialsstorage()FileStorage
SFTPCredentialsstorage()FileStorage

ConnectManager

The ConnectManager is the central singleton registered as app('bridgekit'):

$manager = app('bridgekit');

// Resolve by enum or string
$manager->provider(Provider::Google);
$manager->provider('google');

// Typed shortcuts — OAuth providers
$manager->google();       // → GoogleProvider
$manager->microsoft();    // → MicrosoftProvider

// Typed shortcuts — OAuth storage providers
$manager->dropbox();       // → DropboxProvider

// Typed shortcuts — Credential storage providers (no OAuth)
$manager->ftp();           // → FtpProvider
$manager->s3();            // → S3Provider
$manager->sftp();          // → SftpProvider

// Multi-posting across social providers
$manager->multiPost();    // → MultiPoster

// List all registered providers
$manager->getRegisteredProviders();

// Clear resolved instances
$manager->flush();

Enums

BridgeKit provides 6 backed string enums for type safety:

use BridgeKit\Enums\Provider;       // Google, Microsoft, Meta, LinkedIn, X, Dropbox, Ftp, S3, Sftp
use BridgeKit\Enums\Visibility;     // Public, Connections, Private
use BridgeKit\Enums\EventStatus;    // Confirmed, Tentative, Cancelled
use BridgeKit\Enums\MailFolder;     // Inbox, Sent, Drafts, Trash, Spam
use BridgeKit\Enums\OAuthGrantType; // AuthorizationCode, RefreshToken, ...
use BridgeKit\Enums\ServiceType;    // Auth, Drive, OneDrive, Gmail, ..., Storage
use BridgeKit\Enums\MediaType;      // Image, Video, Gif, Document

The Provider enum has helper methods to distinguish OAuth from credential-based providers:

Provider::Google->requiresOAuth();   // true
Provider::Ftp->requiresOAuth();      // false
Provider::S3->isStorageOnly();       // true

These are used throughout the DTOs and services. For example:

use BridgeKit\DTOs\SocialPost;
use BridgeKit\Enums\Visibility;

$post = new SocialPost(
    content: 'Hello from BridgeKit!',
    visibility: Visibility::Public,
);

Google Drive

Implements FileStorageInterface. Supports Drive API v3 with shared drives.

$drive = BridgeKit::google()->setToken($token)->drive();

// List files in root
$files = $drive->listFiles();

// List files in a folder
$files = $drive->listFiles('folder-id-here');

// Get file metadata
$file = $drive->getFile('file-id');
echo $file->name;       // StorageFile DTO
echo $file->mimeType;
echo $file->size;

// Upload a small file
$file = $drive->uploadFile('notes.txt', 'Hello world', 'text/plain');

// Create a folder
$folder = $drive->createFolder('Reports', 'parent-folder-id');

// Search
$results = $drive->searchFiles('quarterly report');

// Delete
$drive->deleteFile('file-id');

OneDrive

Implements the same FileStorageInterface via Microsoft Graph API:

$onedrive = BridgeKit::microsoft()->setToken($token)->onedrive();

// Same API as Google Drive
$files = $onedrive->listFiles();
$file = $onedrive->uploadFile('doc.pdf', $content, 'application/pdf');
$onedrive->downloadFile('item-id');

SharePoint

Document libraries in SharePoint Online are exposed through Microsoft Graph with the same patterns as OneDrive. Use either a full site ID or a site_path that Graph can resolve. Optional drive_id targets a specific library; otherwise the default site drive is used.

$sp = BridgeKit::microsoft()
    ->setToken($token)
    ->sharepoint([
        'site_path' => '/contoso.sharepoint.com:/sites/marketing',
        // 'drive_id' => 'b!xxx', // optional
    ]);

$files = $sp->listFiles();
$tree = $sp->listTree('', ['max_depth' => 4]);
$libs = $sp->listLibraries(); // enumerate libraries on the site

Required delegated scopes (examples): Sites.Read.All or Sites.ReadWrite.All, plus Files.ReadWrite.All for uploads — align with your app registration.

Folder tree (listTree)

Every implementation of FileStorageInterface provides listTree(string $folderId = '', array $options = []): StorageTreeNode. The tree is built on top of listFilesLazy() via the BuildsFileTree trait, so behaviour is consistent across Drive, OneDrive, SharePoint, Dropbox, S3, FTP, and SFTP.

use BridgeKit\Facades\BridgeKit;

$storage = BridgeKit::s3()->storage();

$root = $storage->listTree('uploads/', [
    'max_depth'     => 5,
    'include_files' => true,
    'root_name'     => 'uploads',
]);

echo $root->toAscii();           // Unix tree-style string for logs / UI
echo $root->countDescendants(); // folders + files below root
echo $root->totalSize();        // cumulative bytes

foreach ($root->walk() as $node) {
    if ($node->isFile()) {
        echo $node->file->webUrl;
    }
}

// JSON API responses
return response()->json($root);

The StorageTreeNode DTO wraps a StorageFile plus child nodes, depth, walk(), toAscii(), and implements JsonSerializable.

Streaming & Large Files

For large files, BridgeKit provides zero-memory-copy operations:

Streaming download

Returns a PHP stream resource — the file is never loaded into memory:

$stream = $drive->downloadStream('file-id');

// Copy to local disk without loading into memory
$out = fopen('/tmp/large-file.zip', 'wb');
stream_copy_to_stream($stream, $out);
fclose($out);
fclose($stream);

Resumable chunked upload

Uploads files of any size using resumable protocols (Google) or upload sessions (OneDrive):

// From a file path
$file = $drive->uploadLargeFile(
    name: 'backup.zip',
    filePathOrStream: '/path/to/backup.zip',
    mimeType: 'application/zip',
    chunkSize: 10 * 1024 * 1024, // 10 MiB chunks
);

// From an open stream
$stream = fopen('/path/to/video.mp4', 'rb');
$file = $onedrive->uploadLargeFile(
    name: 'video.mp4',
    filePathOrStream: $stream,
    mimeType: 'video/mp4',
);

Lazy listing with auto-pagination

Iterates through all pages via a PHP Generator — constant memory regardless of file count:

// Process 10,000+ files without loading them all into memory
foreach ($drive->listFilesLazy() as $file) {
    echo $file->name . "\n";
}

// Collect all files (auto-paginates)
$allFiles = $drive->listFiles(); // uses listFilesLazy internally

Memory comparison

OperationWithout streamingWith streaming
Download 500 MB500 MB RAM~8 KB (stream buffer)
Upload 1 GB1 GB RAM5 MB (chunk size)
List 10,000 filesAll in memory1 file at a time

FTP Storage

Connect to FTP/FTPS servers. No OAuth needed — uses host credentials directly:

// Via config (reads from .env / config/bridgekit.php)
$ftp = BridgeKit::ftp()->storage();

// Or with inline config
$ftp = BridgeKit::ftp([
    'host' => 'ftp.example.com',
    'port' => 21,
    'username' => 'user',
    'password' => 'secret',
    'ssl' => true,
    'passive' => true,
    'root' => '/uploads',
])->storage();

// Same FileStorageInterface as Google Drive / OneDrive
$files = $ftp->listFiles('/reports');
$ftp->uploadFile('data.csv', $csvContent, 'text/csv', '/reports');
$content = $ftp->downloadFile('/reports/data.csv');
$ftp->deleteFile('/reports/old.csv');
$ftp->createFolder('archive', '/reports');

FTPS (FTP over SSL)

Set BRIDGEKIT_FTP_SSL=true to use ftp_ssl_connect() for encrypted connections.

S3 Storage

Works with AWS S3 and any S3-compatible API (MinIO, DigitalOcean Spaces, Backblaze B2, Cloudflare R2):

// Via config
$s3 = BridgeKit::s3()->storage();

// Or with inline config
$s3 = BridgeKit::s3([
    'key' => 'AKIAIOSFODNN7EXAMPLE',
    'secret' => 'wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY',
    'region' => 'eu-west-1',
    'bucket' => 'my-bucket',
])->storage();

// Standard FileStorageInterface
$files = $s3->listFiles('uploads/images');
$s3->uploadFile('photo.jpg', $binary, 'image/jpeg', 'uploads/images');
$content = $s3->downloadFile('uploads/images/photo.jpg');
$s3->deleteFile('uploads/images/old.jpg');
$s3->createFolder('backups');

Public object URLs (webUrl)

Each StorageFile from S3 listing, getFile(), uploads, and folder creation includes webUrl when applicable: virtual-hosted style on AWS (https://{bucket}.s3.{region}.amazonaws.com/{key}), or path-style when a custom endpoint is set (MinIO, R2, DigitalOcean Spaces, etc.). Keys are URL-encoded per segment.

$file = $s3->getFile('reports/summary.pdf');
echo $file->webUrl;

// Time-limited URL for private buckets (S3 concrete service only — not on the interface)
/** @var \BridgeKit\Providers\S3\Services\S3StorageService $s3 */
$s3 = BridgeKit::s3()->storage();
$url = $s3->getPresignedUrl('private/doc.pdf', expiresIn: 900);

S3-compatible endpoints

For non-AWS services, set the endpoint config:

# MinIO
BRIDGEKIT_S3_ENDPOINT=http://localhost:9000

# DigitalOcean Spaces
BRIDGEKIT_S3_ENDPOINT=https://nyc3.digitaloceanspaces.com

# Cloudflare R2
BRIDGEKIT_S3_ENDPOINT=https://<account-id>.r2.cloudflarestorage.com

Multipart upload for large files

S3 multipart uploads are handled automatically by uploadLargeFile():

$s3->uploadLargeFile(
    name: 'database-backup.sql.gz',
    filePathOrStream: '/tmp/backup.sql.gz',
    mimeType: 'application/gzip',
    folderId: 'backups/daily',
    chunkSize: 10 * 1024 * 1024, // 10 MB parts
);

SFTP Storage

SSH File Transfer Protocol with password or public key authentication:

// Via config
$sftp = BridgeKit::sftp()->storage();

// With inline config — password auth
$sftp = BridgeKit::sftp([
    'host' => 'server.example.com',
    'port' => 22,
    'username' => 'deploy',
    'password' => 'secret',
    'root' => '/var/www/uploads',
])->storage();

// With public key auth
$sftp = BridgeKit::sftp([
    'host' => 'server.example.com',
    'username' => 'deploy',
    'private_key' => '/home/user/.ssh/id_rsa',
    'passphrase' => 'optional-passphrase',
])->storage();

// Same API as all storage providers
$files = $sftp->listFiles('/var/www/uploads');
$sftp->uploadFile('deploy.tar.gz', $content, '', '/var/www/releases');
$sftp->downloadStream('/var/log/app.log');

Unified storage interface

All credential and OAuth storage backends (Google Drive, OneDrive, SharePoint, Dropbox, FTP, S3, SFTP) implement the same FileStorageInterface, including listTree(). Switch providers by changing one line:

use BridgeKit\Contracts\Storage\FileStorageInterface;

function backupTo(FileStorageInterface $storage): void
{
    $storage->uploadLargeFile(
        name: 'backup-' . date('Y-m-d') . '.sql.gz',
        filePathOrStream: '/tmp/backup.sql.gz',
    );
}

// Works with any provider:
backupTo(BridgeKit::s3()->storage());
backupTo(BridgeKit::ftp()->storage());
backupTo(BridgeKit::sftp()->storage());
backupTo(BridgeKit::google()->setToken($token)->drive());

Meta / Facebook

Publish to Facebook Pages via the Graph API:

use BridgeKit\DTOs\SocialPost;
use BridgeKit\Enums\Visibility;

$meta = BridgeKit::meta()->setToken($token);

$result = $meta->posts()->publish(new SocialPost(
    content: 'New product launch!',
    mediaUrls: ['https://example.com/photo.jpg'],
    visibility: Visibility::Public,
    metadata: ['page_id' => 'your-page-id'],
));

echo $result->id;   // Post ID
echo $result->url;  // Permalink

LinkedIn

Publish UGC posts via the LinkedIn API v2:

$linkedin = BridgeKit::linkedin()->setToken($token);

$result = $linkedin->posts()->publish(new SocialPost(
    content: 'Exciting company update!',
    visibility: Visibility::Public,
    metadata: ['author_urn' => 'urn:li:person:xxx'],
));

// Manage posts
$post = $linkedin->posts()->getPost($result->id);
$linkedin->posts()->deletePost($result->id);

X / Twitter

Publish tweets via the X API v2 (OAuth 2.0 with PKCE):

$x = BridgeKit::x()->setToken($token);

$result = $x->posts()->publish(new SocialPost(
    content: 'Hello from BridgeKit! 🚀',
));

echo $result->url; // https://x.com/i/web/status/...

Gmail

Send and manage emails via the Gmail API:

use BridgeKit\DTOs\EmailMessage;

$gmail = BridgeKit::google()->setToken($token)->gmail();

// Send an email
$messageId = $gmail->send(new EmailMessage(
    subject: 'Welcome to BridgeKit',
    body: '<h1>Hello!</h1><p>Welcome aboard.</p>',
    to: ['user@example.com'],
    isHtml: true,
));

// List messages
$messages = $gmail->listMessages(MailFolder::Inbox, limit: 20);

// Read a message
$msg = $gmail->getMessage($messageId);
echo $msg->subject;
echo $msg->from;

Outlook

Same EmailSenderInterface via Microsoft Graph:

$outlook = BridgeKit::microsoft()->setToken($token)->outlook();

$outlook->send(new EmailMessage(
    subject: 'Report attached',
    body: 'Please find the report.',
    to: ['boss@company.com'],
));

$messages = $outlook->listMessages('INBOX', 50);

Google Calendar

Implements CalendarInterface with typed CalendarEvent DTOs:

use BridgeKit\DTOs\CalendarEvent;

$calendar = BridgeKit::google()->setToken($token)->calendar();

// List events
$events = $calendar->listEvents('primary', [
    'timeMin' => now()->toRfc3339String(),
    'maxResults' => 10,
]);

// Create an event
$event = $calendar->createEvent(new CalendarEvent(
    title: 'Team Standup',
    description: 'Daily sync',
    startAt: new DateTimeImmutable('2026-04-01T09:00:00'),
    endAt: new DateTimeImmutable('2026-04-01T09:30:00'),
    timezone: 'Europe/Paris',
    attendees: ['alice@company.com', 'bob@company.com'],
));

echo $event->id;
echo $event->webUrl;

Microsoft Calendar

Same CalendarInterface via Microsoft Graph API:

$calendar = BridgeKit::microsoft()->setToken($token)->calendar();

$events = $calendar->listEvents();

$event = $calendar->createEvent(new CalendarEvent(
    title: 'Sprint Review',
    startAt: new DateTimeImmutable('2026-04-05T14:00:00'),
    endAt: new DateTimeImmutable('2026-04-05T15:00:00'),
    timezone: 'America/New_York',
    location: 'Conference Room B',
));

$calendar->deleteEvent($event->id);

Webhooks

BridgeKit provides a unified webhook system that receives notifications from providers and dispatches Laravel events. When a file is moved on Google Drive, a post gets a comment on Meta, or a calendar event is updated — your app reacts automatically.

How it works

Provider (Google, Meta, X...)
  → POST /webhooks/bridgekit/{provider}
    → WebhookController
      → verify signature
      → parse into WebhookPayload
      → WebhookProcessor dispatches Laravel Events
        → WebhookReceived (always)
        → FileCreated / FileMoved / PostPublished / ... (specific)

Setup

The webhook route is registered automatically at /webhooks/bridgekit/{provider}. Configure in config/bridgekit.php:

'webhooks' => [
    'enabled' => true,
    'path' => 'webhooks/bridgekit',   // URL prefix
    'middleware' => [],                // add middleware if needed
],

Listening to events

Register listeners in your EventServiceProvider or use closures:

use BridgeKit\Events\Storage\FileMoved;
use BridgeKit\Events\Storage\FileDeleted;
use BridgeKit\Events\Social\CommentReceived;
use BridgeKit\Events\Calendar\CalendarEventUpdated;
use BridgeKit\Events\WebhookReceived;

// Specific event — file was moved to another folder
Event::listen(FileMoved::class, function (FileMoved $event) {
    $payload = $event->payload;
    Log::info("File {$payload->resourceId} moved", [
        'from' => $event->fromFolder(),
        'to' => $event->toFolder(),
        'provider' => $payload->provider->value,
    ]);

    // Sync the change in your app
    FileRecord::where('external_id', $payload->resourceId)
        ->update(['folder_id' => $event->toFolder()]);
});

// Catch-all — every webhook from every provider
Event::listen(WebhookReceived::class, function (WebhookReceived $event) {
    WebhookLog::create($event->payload->jsonSerialize());
});

Available events

CategoryEvent classTriggered when
StorageFileCreatedNew file uploaded
StorageFileUpdatedFile content or metadata changed
StorageFileDeletedFile permanently deleted
StorageFileMovedFile moved to a different folder
StorageFileTrashedFile moved to trash
SocialPostPublishedNew post created
SocialPostDeletedPost removed
SocialCommentReceivedNew comment on a post
SocialEngagementReceivedReaction, mention, follow change
CalendarCalendarEventCreatedNew calendar event
CalendarCalendarEventUpdatedEvent time/details changed
CalendarCalendarEventCancelledEvent cancelled
GenericWebhookReceivedEvery webhook (catch-all)

Subscribing to provider webhooks

Each provider's webhooks() service lets you register (subscribe) and manage webhook subscriptions:

Google Drive — watch for file changes

$google = BridgeKit::google()->setToken($token);

// Watch all Drive changes
$reg = $google->webhooks()->subscribe(
    callbackUrl: 'https://app.com/webhooks/bridgekit/google',
    options: ['type' => 'drive', 'ttl' => 86400],
);

// Watch a specific file
$reg = $google->webhooks()->subscribe(
    callbackUrl: 'https://app.com/webhooks/bridgekit/google',
    options: ['resource_id' => 'file-id-here'],
);

// Watch calendar events
$reg = $google->webhooks()->subscribe(
    callbackUrl: 'https://app.com/webhooks/bridgekit/google',
    options: ['type' => 'calendar', 'calendar_id' => 'primary'],
);

// Unsubscribe
$google->webhooks()->unsubscribe($reg->id);

Microsoft Graph — subscriptions

$ms = BridgeKit::microsoft()->setToken($token);

// Watch OneDrive root for changes
$reg = $ms->webhooks()->subscribe(
    callbackUrl: 'https://app.com/webhooks/bridgekit/microsoft',
    options: ['resource' => 'me/drive/root', 'change_type' => 'updated'],
);

// Watch calendar
$reg = $ms->webhooks()->subscribe(
    callbackUrl: 'https://app.com/webhooks/bridgekit/microsoft',
    options: ['resource' => 'me/events', 'change_type' => 'created,updated,deleted'],
);

// Renew before expiration
$ms->webhooks()->renew($reg->id, ttlMinutes: 4230);

Meta — Page webhooks

$meta = BridgeKit::meta()->setToken($token);

// Get the verify token to configure in Meta App Dashboard
$reg = $meta->webhooks()->subscribe(
    callbackUrl: 'https://app.com/webhooks/bridgekit/meta',
    events: ['feed', 'mention', 'messages'],
);

// Use $reg->secret as the Verify Token in Meta Dashboard

X / Twitter — Account Activity

$x = BridgeKit::x()->setToken($token);

$reg = $x->webhooks()->subscribe(
    callbackUrl: 'https://app.com/webhooks/bridgekit/x',
    options: ['env_name' => 'production'],
);

The WebhookPayload DTO

Every webhook is parsed into a typed WebhookPayload:

$payload->provider;      // Provider::Google
$payload->event;         // WebhookEvent::FileMoved
$payload->resourceId;    // 'abc123'
$payload->resourceType;  // 'file'
$payload->data;          // provider-specific data array
$payload->timestamp;     // DateTimeImmutable
$payload->changes;       // what changed (e.g. parent_from, parent_to)
$payload->raw;           // original request body

// Helper methods
$payload->getChange('parent_from');  // 'old-folder-id'
$payload->hasChanges();             // true

Token Auto-Refresh

BridgeKit automatically refreshes expired OAuth tokens before each API call. A 60-second buffer ensures tokens are refreshed before actual expiration:

// Just use services normally — token refresh is automatic
$google = BridgeKit::google()->setToken($token);

// Even hours later, this works without manual refresh
$files = $google->drive()->listFiles();

// The token is refreshed transparently if expired.
// The provider's token is updated in place.

This works for all OAuth providers (Google, Microsoft, Meta, LinkedIn, X, Dropbox). Requires the token to have a valid refreshToken and expiresAt.

Retry & Rate Limiting

All HTTP requests include automatic retry with exponential backoff for transient errors (429, 500, 502, 503, 504):

// Default: 3 retries, 500ms base delay
$files = BridgeKit::google()
    ->setToken($token)
    ->drive()
    ->listFiles();

// Custom retry config on any service
$service = BridgeKit::google()->setToken($token)->drive();
$service->withRetry(maxRetries: 5, baseDelayMs: 1000);

Rate-limited responses throw RateLimitException with a retryAfter property:

use BridgeKit\Exceptions\RateLimitException;

try {
    $result = $service->listFiles();
} catch (RateLimitException $e) {
    $e->retryAfter;  // seconds to wait
    $e->provider;    // 'google'
}

Multi-Posting

Broadcast a single post across multiple social providers in one call. Content is automatically adapted per platform (character limits, media count):

use BridgeKit\Facades\BridgeKit;
use BridgeKit\DTOs\SocialPost;
use BridgeKit\Enums\Provider;

$result = BridgeKit::multiPost()
    ->on(Provider::Meta, BridgeKit::meta()->setToken($metaToken)->posts())
    ->on(Provider::LinkedIn, BridgeKit::linkedin()->setToken($linkedInToken)->posts())
    ->on(Provider::X, BridgeKit::x()->setToken($xToken)->posts())
    ->publish(new SocialPost(
        content: 'Exciting news! BridgeKit v1.2 is out with multi-posting support.'
    ));

// Check results
$result->isFullSuccess();         // true if all providers succeeded
$result->isPartialSuccess();      // true if some succeeded, some failed
$result->succeededProviders();    // ['meta', 'linkedin']
$result->failedProviders();       // ['x']

// Get per-provider details
$result->getResult('meta');       // SocialPostResult
$result->getError('x');           // Throwable

Platform limits

PlatformMax charactersMax media
X (Twitter)2804
LinkedIn3,0009
Meta (Facebook)63,20610

Content exceeding the limit is truncated with an ellipsis. Excess media is trimmed.

Dropbox Storage

Dropbox is a full OAuth provider with FileStorageInterface support:

use BridgeKit\Facades\BridgeKit;

// OAuth flow
$url = BridgeKit::dropbox()->auth()->getAuthorizationUrl([
    'files.metadata.read',
    'files.content.read',
    'files.content.write',
]);

$token = BridgeKit::dropbox()->auth()->handleCallback($code);

// Use storage
$dropbox = BridgeKit::dropbox()->setToken($token);

$files = $dropbox->storage()->listFiles('/Documents');
$dropbox->storage()->uploadFile('report.pdf', $content, 'application/pdf', '/Documents');
$dropbox->storage()->createFolder('Projects', '/Documents');
$results = $dropbox->storage()->searchFiles('invoice');

Large file uploads (chunked sessions)

Files over 150 MB should use the upload session API:

$file = $dropbox->storage()->uploadLargeFile(
    name: 'database-backup.sql.gz',
    filePathOrStream: '/tmp/backup.sql.gz',
    folderId: '/Backups',
    chunkSize: 8 * 1024 * 1024  // 8 MB chunks
);

Custom Providers

Create your own provider by extending AbstractProvider (OAuth) or AbstractStorageProvider (credentials):

use BridgeKit\Support\AbstractProvider;
use BridgeKit\Contracts\Auth\OAuthInterface;

class DropboxProvider extends AbstractProvider
{
    public function getName(): string
    {
        return 'dropbox';
    }

    public function auth(): OAuthInterface
    {
        return $this->resolveService('auth', fn () => new DropboxAuthService($this->config, $this));
    }

    public function storage(): DropboxStorageService
    {
        return $this->resolveService('storage', fn () => new DropboxStorageService($this));
    }

    public function getAvailableServices(): array
    {
        return [
            'auth' => DropboxAuthService::class,
            'storage' => DropboxStorageService::class,
        ];
    }
}

For a credential-based storage provider, extend AbstractStorageProvider instead:

use BridgeKit\Support\AbstractStorageProvider;
use BridgeKit\Contracts\Storage\FileStorageInterface;

class WebDAVProvider extends AbstractStorageProvider
{
    public function getName(): string
    {
        return 'webdav';
    }

    public function storage(): FileStorageInterface
    {
        return $this->resolveService('storage', fn () => new WebDAVStorageService($this->config));
    }

    public function getAvailableServices(): array
    {
        return ['storage' => WebDAVStorageService::class];
    }
}

Register any custom provider in your AppServiceProvider:

use BridgeKit\Facades\BridgeKit;

public function boot(): void
{
    BridgeKit::extend('dropbox', DropboxProvider::class);
    BridgeKit::extend('webdav', WebDAVProvider::class);
}

Creating services with AbstractService

Extend AbstractService to get automatic token handling and HTTP client:

use BridgeKit\Support\AbstractService;
use BridgeKit\Contracts\Storage\FileStorageInterface;

class DropboxStorageService extends AbstractService implements FileStorageInterface
{
    // getProviderName() and getAccessToken() are inherited
    // authenticatedHttp() is ready to use

    public function listFiles(string $folderId = '', array $options = []): array
    {
        $response = $this->authenticatedHttp()
            ->post('https://api.dropboxapi.com/2/files/list_folder', [
                'path' => $folderId ?: '',
            ]);

        return array_map(fn ($e) => $this->mapEntry($e), $response->json('entries'));
    }

    // ... implement remaining methods
}

Error Handling

BridgeKit throws typed exceptions for different error scenarios:

use BridgeKit\Exceptions\AuthenticationException;
use BridgeKit\Exceptions\ProviderException;
use BridgeKit\Exceptions\InvalidConfigException;
use BridgeKit\Exceptions\BridgeKitException;

try {
    $files = BridgeKit::google()->drive()->listFiles();
} catch (AuthenticationException $e) {
    // No token set, or token expired
} catch (ProviderException $e) {
    // API error from the provider
    echo $e->provider; // 'google'
    echo $e->getCode(); // HTTP status code
} catch (InvalidConfigException $e) {
    // Unknown provider or missing config
} catch (BridgeKitException $e) {
    // Catch-all for any BridgeKit error
}

Exception hierarchy

BridgeKitException (extends RuntimeException)
├── AuthenticationException
├── ProviderException        → has $provider property
└── InvalidConfigException