Uninstall, unpublish, and billing
The user uninstalls
They click Uninstall on Settings → Connections or the manage page. You
receive installation.deleted (installation_id only). Tokens for that
install are revoked. Old comments and checks stay on the pull request.
if (event === "installation.deleted") {
await db.installs.markDeleted(payload.installation_id)
return
}
They can also remove one repository from the install. You get
installation.repos_changed with the repository ids that remain.
if (event === "installation.repos_changed") {
await db.installs.update(payload.installation_id, {
repositoryIds: payload.repositories ?? [],
})
}
If they revoke Sign in with open-git (Settings → Connections → Authorized
applications), that is not an uninstall unless they choose Revoke and
uninstall. Revoke access only clears Connected on installs they can
manage. Stop treating them as signed in. The bot grant is unchanged until
they uninstall. Revoke and uninstall sends installation.deleted for
each of those installs, then removes them.
You unpublish
On the bot page, Unpublish:
- POSTs
installation.deletedto every install (and waits for those deliveries) before marking the bot unpublished - Sets every install to
disabledand revokes API tokens - Disables the linked OAuth client (consent shows the app as unavailable)
- Keeps the listing URL reserved.
/bots/{slug}shows “This bot is no longer published.” The listing path 404s.
There is no republish control. Treat unpublish as taking the product down.
Handle those installation.deleted events the same as a user uninstall.
You change permissions or rotate keys
Changing the permission set revokes outstanding installation tokens. Mint again on the next event. Rotating the install private key also revokes every token and issues a new key (shown once). Rotating the webhook secret replaces the 64-hex secret; your server must start verifying with the new value.
// After you paste a new webhook secret into your host
process.env.OPEN_GIT_BOT_WEBHOOK_SECRET = "the-new-secret"
Billing stays on your side
open-git will not add entitled: false to an install. That would let you
kill their grant from our API.
If a trial ends, skip the job — or post a check that points at your subscribe page:
async function reviewPullRequest(options: {
installationId: string
owner: string
repository: string
number: number
sha: string
}) {
const install = await db.installs.get(options.installationId)
const { api } = await installationClient(options.installationId)
if (!install?.entitled) {
await api.upsertCheck(options.owner, options.repository, options.sha, {
name: "your-bot",
status: "failed",
summary: "Subscribe to run this check.",
detailsUrl: "https://your-app.com/subscribe",
})
return
}
await api.upsertCheck(options.owner, options.repository, options.sha, {
name: "your-bot",
status: "success",
summary: "All clear.",
})
}
If they unlink in your product, stop working and clear Connected:
await db.installs.update(installationId, { entitled: false })
await markDisconnected(installationId)
We will not delete the install for you.
One open-git user can be two of your customers (work and personal). Link per
installation_id, not “this sub paid once globally.”
// Work and personal are two rows, same sub
await db.customers.upsert({
openGitUserId: sub,
installationId,
plan: "team",
})
One of your orgs will have many installs (one per company account). That is expected.