Installing an app
Some apps built for Jelly install in one step. You give Jelly the app's address, Jelly shows you everything the app asks for, and when you press Install it sets all of it up together: the sign-in the app uses, the webhook it hears on, and the contact card it draws. Nobody has to copy ids and secrets from one page to another.
An installed app is an app your team registers, set up for you from the app's own description of itself. That description is called its manifest.
Installing an app
Admins and the Owner install apps.
- Go to Settings → Apps and open Install an app.
- Type the address of the app's manifest, which the app's developer
gives you. It starts
https://. If they gave you the manifest itself instead, paste it under Or paste the manifest. - Press Review the app. Jelly fetches the manifest and shows you what the app asks for. Nothing is set up yet.
- Read what the app asks for. See what an app can do.
- If the app asks to hear about events, choose which mailboxes it hears about: Every mailbox, or Only the mailboxes I choose and tick them. Leave out any mailbox the app mustn't hear about.
- Press Install.
Jelly sets everything up at once. If any part of it can't be set up, none of it is, and Jelly tells you why.
Jelly then sends the app its secrets for you. If it can't reach the app, the app's page shows you the secrets instead, with Copy beside each. Give them to the app's developer straight away, because they're shown only this once.
If a manifest asks for something Jelly won't allow, Jelly refuses it and tells you which part is wrong. Pass that on to the app's developer.
What an app can do
Before you press Install, Jelly lists everything the app will get:
- What it may ask people to allow, in the same words people see when the app asks to sign them in. These are the four scopes. The app can't do anything until a person allows it, and then it can do no more than that person can.
- Which events it hears about, and from which mailboxes. A webhook sends the app what happened, with the conversation it happened in. It never hears about a mailbox you didn't choose.
- Where its contact card comes from, if it draws one. A card is shown to everyone who can see the conversation.
- Where Jelly sends its secrets, its support address, and who is installing it.
Everything the app says about itself is shown as plain text, and its addresses are shown in full, so you can see where your team's data will go.
When an app changes
An app's developer can change its manifest, but nothing changes for your team until you agree to it.
On the app's page, press Check for changes. If the manifest has changed, the page shows what's different, and says This app asks for more when it wants a scope, an event or an address it didn't have before. Press Review to see the whole of it again, and Update to agree.
When an app gains a scope, nobody's sign-in gets it until they allow the app again. When it loses one, every sign-in loses it at once.
Removing an app
On Settings → Apps, open the app's page and press Remove this app…. Jelly tells you what will stop, and asks you to confirm.
Removing an app:
- signs out everyone signed in through it, and emails each of them;
- deletes its webhook and its contact card;
- tells the app it was removed.
What the app already did stays, under the names of the people it acted for. To use the app again, install it again. It gets new ids and new secrets.
You can also delete only an app's webhook or its card, on that webhook's or card's own settings page. The rest of the app keeps working without it.
For developers
This part is for whoever builds the app.
The manifest
Host the manifest as JSON at an https:// address, or give it to the
Admin to paste.
{
"name": "Acme Console",
"description": "The console Acme's staff work from.",
"redirect_uris": ["https://console.acmetools.test/oauth/callback"],
"scopes": ["read", "write"],
"webhook": {
"url": "https://console.acmetools.test/jelly/events",
"events": ["conversation.archived", "note.added"]
},
"card_source": {"url": "https://console.acmetools.test/jelly/card"},
"install_url": "https://console.acmetools.test/jelly/install",
"support_url": "https://acmetools.test/support"
}
| Field | |
|---|---|
name |
Required. Up to 60 characters. People see it when the app signs them in, and beside everything it does |
description |
Up to 500 characters of plain text. Line breaks are kept |
redirect_uris |
Required. The same rules as registering an app |
scopes |
Required. Some of read, write, send and settings |
webhook |
Optional. url, and events: change kinds |
card_source |
Optional. The url Jelly asks for a contact card |
install_url |
Optional. Where Jelly sends the installation's secrets |
support_url |
Optional. Shown to the Admin |
Every address must start https:// and be on the public internet.
Jelly fetches the manifest under the same rules as a contact card: it
doesn't follow a redirect, reads at most 64 KB, and waits at most three
seconds. Fields Jelly doesn't know are ignored.
Jelly keeps the manifest exactly as it was installed. When you change it, nothing changes on any team until an Admin there agrees to the change.
Receiving the secrets
Straight after the install, Jelly sends a POST to install_url:
{
"event": "app.installed",
"installation_id": 4,
"client_id": "app_k3f9zq2m7p4x8r1t6w5y",
"installation_secret": "5d1c…",
"webhook_id": 2,
"webhook_secret": "9b1e…",
"card_source_id": 3,
"card_secret": "3f6c…",
"instance_url": "https://jelly.acme.test",
"sent_at": "2026-03-01T09:00:00Z"
}
webhook_id and webhook_secret are null when the app has no
webhook, and the same goes for the card. Answer with a 2xx status.
If you don't, or Jelly can't reach you within three seconds, the Admin
is shown the secrets instead and gives them to you.
Each request to install_url carries these headers:
| Header | |
|---|---|
X-Jelly-Event |
app.installed, app.updated or app.uninstalled |
X-Jelly-Signature |
sha256= and the hex HMAC-SHA256 of the raw body, using the installation secret |
X-Jelly-Timestamp |
Unix seconds, when it was sent |
X-Jelly-Installation |
The installation's id |
The first request carries the installation secret it's signed with,
so its signature shows only that the body arrived whole. Keep the
installation secret, and check every later request with it. When a
person first signs in through the app, call
GET /api/app on instance_url with their
token: it shows the same installation, client and webhook ids.
Jelly sends app.updated when an Admin agrees to a changed manifest.
If the change added a webhook or a card, the request carries its id
and secret, as at install. Jelly sends app.uninstalled when the app
is removed. That one is sent once, and isn't tried again.