Version

Installations

The installation rows as a connector may read and write them — finding by id, platform id or storage ref, listing a project's or the platform's, recording one, recording a health verdict, forgetting one.

Subscriby\Connector\Core\Installations. An installation is a connector set up on a project (the project's bot, the project's server) or, for a platform-scope connector, Subscriby's own shared presence on the platform. The core owns the row, its encrypted credentials, its state and its health; a connector reads it here and writes what it learns.

Reads

MethodReturnsNull when
find(string $id)?InstallationRefNo installation has that UUID.
findByExternalId(string $connector, string $externalId)?InstallationRefThe platform id is unknown to the core.
findByStorageRef(string $connector, string $storageRef)?InstallationRefNothing points at that row of yours.
listForProject(ProjectRef $project, ?string $connector = null)list<InstallationRef>Never null; live and standby, one connector's or every connector's.
listPlatform(string $connector)list<InstallationRef>Never null; the connector's platform-scope installations.
credentials(InstallationRef $installation)CredentialBagThrows ResourceNotFoundException for an unknown ref; empty for a disconnected installation.

findByStorageRef() is the read your inbound handler makes most: a webhook arrives for a bot, your table knows the bot's row, and the ref tells you which core installation (and so which project) it belongs to.

credentials()

$bag = $installations->credentials($installation);
$secret = $bag->get('webhook_secret');

The stored bag of an installation the connector can already name: what the creator pasted into the install form plus what the connector put in its InstallationSummary::$meta when it connected (InstallationSummary::credentialsFor() merges the two before the row is written). It exists for the one moment a connector needs a secret before the core has handed it anything: an inbound gateway verifying a signature. It widens nothing a port call would not have given, because every port receives the same bag for the same installation.

record()

$ref = $installations->record(new InstallationRecord(
    connector: 'example',
    scope: InstallationScope::Project,
    externalId: $bot->id,
    displayName: $bot->name,
    projectId: $project->id,
    role: InstallationRole::Live,          // or Standby
    state: InstallationState::Connected,
    stateReason: null,
    handle: $bot->username,
    avatarUrl: $bot->avatarUrl,
    storageRef: (string) $row->id,
    credentials: null,                      // only when the connector holds secrets the core should store
    settings: [],
    connectedAt: now(),
    verifiedAt: now(),
));

Idempotent on the connector key and the platform id: a second record of the same bot or app updates the row rather than adding one. When a connector's InstallationLifecycle::complete() answers, the core records the installation from the summary itself, so a connector calls record() directly only when it learns of an installation outside that flow.

recordState()

$installations->recordState($ref, InstallationState::Revoked, reason: 'token_revoked');

Your health probes call this instead of re-describing the whole installation, so a probe that only learnt "the token was revoked" writes exactly that. reason is your own code; the core shows a generic sentence for it and prefers the words your RecoverySupport::healthReasonText() gives.

forget()

Drop an installation whose row on the connector's side is gone. It exists for the first-party connector, whose own tables led the neutral rows for a while; disconnect and uninstall keep the row, so once the neutral rows lead this call has no caller. A connector built on the SDK from day one never calls it.

What the ref carries

InstallationRef: id, connector, scope, projectId, externalId, storageRef, handle. Enough to act on the platform (with the CredentialBag the core hands your port) and to find your own row. Credentials, settings and health are never on the ref: the core hands credentials to ports per call (or through credentials() when a gateway asks first), and health is written, not read, from a connector.

One row per bot, whatever happens to it

Disconnect wipes credentials and sets Disconnected; uninstall stamps the row and keeps it; a reconnect names the same row through InstallationRequest::$existing. The platform id is the identity of the row for its whole life, which is why record() keys on it.

How is this guide?

Last updated on

Version

On this page

Subscriby is a product
designed by you — for you.
No boardroom full of executives deciding what we ships next. Our roadmap always shaped by you with your feedback.

Share feedback or a request

For AI agents: llms.txt · llms-full.txt