Skip to content

App events

An OAuth app can be told when an account installs it, changes what it granted, disconnects it, or loses API access. Without these, an uninstall reaches you only as a 401 on your next call.

The same URL can also receive the account events — calls, appointments, contacts, services, resources, locations, documents and account settings, working hours included — of every account that installed the app, so one receiver serves all your customers.

App events are separate from an account’s webhooks: they go to the app’s URL, not to one the customer configured, and no customer plan is involved.

In the console, set the app’s events URL. The same rules as a webhook endpoint apply: https://, port 443, public addresses only. The signing secret (whsec_…) is generated the first time a URL is set and shown once.

Changing the events URL does not send the app back to review.

Always sent while the app has an events URL; there is nothing to subscribe to.

Event When Extra data
installation.created An account installs the app, or reinstalls it after a disconnect —
installation.scopes_changed An account reconnects with a different set of scopes. A reconnect with the same scopes sends nothing previous_scopes
installation.revoked The installation is disconnected reason
installation.access_suspended The account can no longer use the API reason
installation.access_restored It can again —

Every event carries data.installation:

{
"object": "installation",
"id": "5d0e8a2c-7b41-4f7e-9c3a-1e2f4b6d8a90",
"client_id": "talki_cid_4Hq2mZ8rVt1LxN3pYw7KcA",
"account_id": "f7e39b9a-1ac8-45e4-93da-2600f17cb286",
"scopes": ["appointments:read", "calls:read"],
"status": "active",
"access": "active",
"livemode": true,
"created_at": "2026-09-15T09:14:31.204518+00:00",
"revoked_at": null
}
Field Type Meaning
object string Always installation
id string The installation: one account’s connection to your app. Equal to the envelope’s installation_id
client_id string Your app’s client ID
account_id string The Talki account that installed the app
scopes array of strings The scopes the account granted, sorted
status string active, or revoked once the installation is disconnected
access string suspended while the account cannot use the API, otherwise active
livemode boolean false for a sandbox account’s installation
created_at string When the account first installed the app
revoked_at string or null When the installation was disconnected; null while it is active

created_at and revoked_at are ISO 8601 with a +00:00 offset and may carry microseconds, like the envelope’s created_at, not the Z form of the REST API. Parse them as instants.

installation.scopes_changed adds previous_scopes, the scopes granted before the reconnect, sorted; the new ones are in installation.scopes.

On installation.revoked:

reason Who disconnected
customer The account owner, from the Talki app
partner Your app, by calling /oauth/revoke with one of its tokens
account_deleted The account was closed

On installation.access_suspended, reason is the error code your API calls now receive: subscription_inactive, plan_upgrade_required or account_closed. Stop syncing until installation.access_restored; the grant and its tokens survive a suspension.

An access event can arrive up to 15 minutes after the change, and is sent only when the state actually changes.

In the console, open the app’s Events and tick the types it should receive. Each type is opt-in, and there is no wildcard: a type added to the catalogue later is not sent until you tick it.

A type is sent for an account only while its installation grants the type’s scope. A type whose scope the app does not request cannot be ticked.

Event Scope
call.ringing calls:read
call.ended calls:read
call.transferred calls:read
call.updated calls:read
call.deleted calls:read
appointment.created appointments:read
appointment.updated appointments:read
appointment.cancelled appointments:read
contact.created contacts:read
contact.updated contacts:read
contact.deleted contacts:read
pending_contact.created, .updated, .deleted contacts:read
tag.created, .updated, .deleted contacts:read
caller_rule.created, .updated, .deleted contacts:read
unresolved_request.updated unresolved:read
document.created, .updated, .deleted knowledge:read
account.updated account:read
working_hours.updated account:read
closure.created, .updated, .deleted account:read
service.created, .updated, .deleted services:read
resource.created, .updated, .deleted resources:read
location.created, .updated, .deactivated, .reactivated account:read
location.settings_updated, location.working_hours_updated account:read

data is the same as on a webhook; see the event catalogue. usage.* and ping are not offered to apps. A billing partner’s app receives usage.* for the accounts it pays for without subscribing; see Managed billing.

The grant is checked when the event is written and again before each attempt. Once an installation is revoked or no longer grants the scope, or the account loses API access, its queued events are not sent.

{
"id": "evt_3c7a1f0e9d2b4c6a8e5f1b0d7a2c4e6f",
"type": "call.ended",
"created_at": "2026-09-20T10:41:07.118204+00:00",
"api_version": "2026-10-01",
"account_id": "f7e39b9a-1ac8-45e4-93da-2600f17cb286",
"livemode": true,
"origin": {
"source": "voice",
"credential_id": null,
"idempotency_key": null
},
"installation_id": "5d0e8a2c-7b41-4f7e-9c3a-1e2f4b6d8a90",
"data": {
"object": { "id": "38d23d84-02d2-4418-b169-3608aa04a8f8", "…": "…" }
}
}
  • installation_id is the connection the event belongs to: look up the customer and its token by it.
  • account_id is the Talki account the event happened on.
  • livemode is false for a sandbox installation.

Events can arrive out of order, and an account event can arrive before its installation.created. Key on installation_id, and order events as on webhooks: by the object’s updated_at, or the envelope’s created_at for an object without one, never by arrival.

A change your app makes through the API comes back as an event with origin.source api, origin.credential_id set to your installation_id, and origin.idempotency_key set to the request’s key. Drop the events whose origin.credential_id is the installation’s own ID.

Other apps installed on the same account receive that event with origin.source api and both credential_id and idempotency_key null.

Events are not written while delivery is paused, while the app has no events URL, or for a type not yet subscribed. After a gap, read each installation’s change feeds from its last stored cursor, with that installation’s token, and upsert by Talki ID.

If an account also has a webhook endpoint with the app’s events URL, both deliver: the same change arrives twice, with different evt_ IDs. Delete those endpoints once the app receives the events itself.

The same envelope as webhooks, with two differences: account_id is the installed account, and installation_id is added. origin is null.

{
"id": "evt_9b2f6c1e4a7d4e0f8c3b5a2d1e6f7a8b",
"type": "installation.revoked",
"created_at": "2026-09-20T16:02:11.482913+00:00",
"api_version": "2026-10-01",
"account_id": "f7e39b9a-1ac8-45e4-93da-2600f17cb286",
"livemode": true,
"origin": null,
"installation_id": "5d0e8a2c-7b41-4f7e-9c3a-1e2f4b6d8a90",
"data": {
"installation": { "object": "installation", "status": "revoked", "…": "…" },
"reason": "customer"
}
}

api_version is the app’s pin, on every app event.

Signed and delivered exactly like webhooks: the same headers, the same X-Talki-Signature, the same retries and timeouts. The key is the app’s events secret. Use the same verifier, and accept any v1.

Rotating the events secret stops the old one at once unless you ask for an overlap (up to 7 days) when you rotate.

  • After 20 consecutive failed deliveries, event delivery to the app pauses, and the app’s owner is emailed. Only failures at your receiver count: a non-2xx answer (a 410 Gone included), the 10-second timeout, a connection, TLS or DNS failure, or a URL that no longer resolves to a public address. A delivery counts once all six attempts are spent, and one success resets the counter. Resume from the app’s events in the console.
  • Events from a pause, or from before an events URL is set, are not sent later. For account events, catch up from the change feeds. For installation events, the next access check after resuming reports the current access state; installs and disconnects from the gap are not sent. Reconcile them by calling the API with each installation’s token: a disconnected one answers 401 invalid_credentials.
  • A disabled app receives nothing.

Deliveries on the app, in the console, lists what was sent to the events URL in the last 30 days, newest first, filterable by status and installation. Each row shows the event ID and type, the installation, the attempts, and either your receiver’s status with the first 500 characters of its body, or an error class. The payload is not shown: fetch the object through the API.

Account events add three classes. None is retried, and none counts toward the pause:

Class Meaning
installation_inactive The installation was revoked, or no longer grants the event’s scope
event_unsubscribed The app no longer subscribes to the event type
account_refused The account can no longer use the API

Redeliver sends the stored event again with a fresh set of six attempts and the same id. It answers 409 while the delivery is still being attempted, while the app is paused, disabled or has no events URL, and for an account event that one of the three classes above would stop now.

A receiver that fails for one account while serving the others never pauses the app. Once a day, the app’s owner is emailed the installations whose deliveries failed at the receiver, each with a count, the last error and a link to its log. Live and sandbox installations are listed separately, and no digest is sent while the app is paused.

A digest covers the events created 36 to 12 hours before it is sent, when every retry has finished, and 61 to 37 hours before for expired events. Refusals are not counted.