Identities

The identity rows and who holds them — finding an account, recording it, linking it to a creator or a member, the handshake that proves possession, and the two lookups that turn an account into a person.

Subscriby\Connector\Core\Identities. An identity is a person's account on a connector. A connector says which account the platform is talking about; the core decides which creator or member holds it. Recording and linking are separate calls, because the same account can be a creator in one place and a member in another, and because a purchase may know the account before anybody has proven who holds it.

Reads

MethodReturnsNull when
find(string $id)?IdentityRefNo identity has that UUID.
findByExternalId(string $connector, ?string $installationId, string $externalId)?IdentityRefThe account is unknown. Pass null for a platform-wide id, the installation's UUID for a per-installation one.
findCreator(IdentityRef $identity)?CreatorRefNo creator holds the account.
findMember(IdentityRef $identity, ProjectRef $project)?MemberRefNo member of that project holds it. The link is per project, because a person is a different member in each.
isHandshakeToken(string $token)bool

The pair findByExternalId() then findCreator()/findMember() is how an inbound event becomes a person: your resolver names the account, this contract names who holds it, and your handler acts as that creator or for that member.

record()

$ref = $identities->record(new IdentityRecord(
    connector: 'example',
    externalId: $user->id,
    installationId: null,               // null for a platform-wide id
    displayName: $user->name,
    username: $user->handle,
    avatarUrl: $user->avatar,
    storageRef: (string) $row->id,
    meta: [],
    lastSeenAt: now(),
));

Idempotent on the connector key, the installation and the platform id; a second record refreshes the name, picture and lastSeenAt. Record every account you see act, whether or not anyone holds it yet: a purchase from an unknown account is filed as a pending grant against the identity and materialises when someone proves they hold it.

Linking

$identities->linkCreator($identity, $creator, IdentityPurpose::Primary, IdentityLinkSource::Handshake);
$identities->linkMember($identity, $member, IdentityLinkSource::Bot, preferred: true);
  • linkCreator() says a creator holds the account, as their Primary or Backup identity on the connector. Idempotent; an identity already held by another creator is refused, because one account signs in as one person.
  • linkMember() says a member of a project holds it, and whether the member wants to be reached there first. Idempotent per project.

IdentityLinkSource records how the link was proven: Bot (the account acted in the connector and the core matched it), Portal (the member linked it from the portal), Handshake (a two-sided proof), Adopted (the member tapped "use the same account" from a sibling project), Backfill (a migration). Write the truth; the dashboard shows it and the audit trail keeps it.

Handshakes

A handshake is the two-sided proof that joins an account to a person: the core opens it with a purpose and a token, the connector completes it with the account that presented the token, and the core writes the link.

if ($identities->isHandshakeToken($word)) {
    $completion = $identities->completeHandshake($word, $identityRecord, $seenBy);
    // $completion->handshake (HandshakeRef), ->subjectName, ->returnUrl
}
  • isHandshakeToken() says whether a token names a handshake the core ever issued, in whatever state. Ask it before claiming a typed word, so a wizard answer that happens to look like a code is left to its wizard.
  • completeHandshake($token, IdentityRecord $identity, ?InstallationRef $seenBy) proves possession and lets the core decide what the handshake was for. A creator link makes the account the creator's primary identity; a portal sign-in makes (or finds) the account's member in the handshake's project and answers where they go next; a recovery relink or a backup identity writes those. Pass $seenBy when you can say which installation heard the account, so a project-bound handshake completes only through that project's own installation.

HandshakeRefused is thrown when the token names no pending handshake (expired()), the account already belongs to another person (identityHeld()), another project's installation heard it (installationMismatch()), or the purpose is not completed through this call (purposeNotSupported()). Show the member a plain "this link has expired, start again" and never retry with a guess.

What the ref carries

IdentityRef: id, connector, externalId, storageRef. Names, pictures and who holds the account are not on it: the platform's view comes from your IdentityResolver::describe(), the core's from findCreator()/findMember().

Never link from a claim

A message that says "I am the owner" proves nothing. The only paths that create a creator link are a handshake the core minted, a sign-in the platform vouched for (adopted through your IdentityResolver), and a registration the connector ran. Everything else is a member link at most.

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