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?

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