> ## Documentation Index
> Fetch the complete documentation index at: https://docs.attio.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Cleaning up on uninstall

> What runs when a workspace uninstalls your app, and where to put your cleanup

When a workspace uninstalls your app, Attio calls the same handlers that it calls when a workflow
is paused or a connection is removed. There is no separate uninstall event. If your
[`deactivate`](https://docs.attio.com/sdk/workflows/define-workflow-block-deactivate) and
[`connection-removed`](https://docs.attio.com/sdk/server/events/connection-removed) handlers clean up what your app
created, that cleanup also runs on uninstall.

## What happens on uninstall

Attio uninstalls an app in this order:

1. **Pause workflows.** Attio pauses every live workflow that uses your app. As with a manual
   pause, it calls `deactivate` for each trigger block in the workflow.
2. **Wait for teardowns.** Attio waits for each `deactivate` call to finish, for up to 10 minutes.
3. **Remove connections.** Attio removes every connection of the installation, both workspace and
   user connections. It calls `connection-removed` for each one before it removes the connection.

## Where to put your cleanup

Use this table to find the handler that must delete each resource:

| You created it in | Clean it up in |
| - | - |
| A trigger block's [`activate`](https://docs.attio.com/sdk/workflows/define-workflow-block-activate) | The same block's [`deactivate`](https://docs.attio.com/sdk/workflows/define-workflow-block-deactivate) |
| [`connection-added`](https://docs.attio.com/sdk/server/events/connection-added) | [`connection-removed`](https://docs.attio.com/sdk/server/events/connection-removed) |

For example, if a trigger block registers a webhook for one workflow, delete that webhook in
`deactivate`. Use the `uniqueActivationId` from `metadata` to find it. If your app registers a
webhook one time for each connection, delete that webhook in `connection-removed`.

> [!WARNING]
>
> Attio also calls `connection-removed` when a user reconnects. If
> `connection-removed` deletes a trigger block's webhooks, every workflow that uses them stops triggering, even though the
> workflow is still live. Delete those webhooks in `deactivate` instead.

## When cleanup fails

### `deactivate`

* If `deactivate` returns `{type: "error"}`, Attio does not call it again.
* If `deactivate` throws, Attio calls it again, for a maximum of 5 attempts. There is no delay
  between attempts. Thus, the attempts help only with failures that continue for a few seconds or
  less.

In both cases, the workflow stays paused and the uninstall continues.

### `connection-removed`

During an uninstall, Attio always removes the connection:

* If `connection-removed` throws, Attio calls it again, for a maximum of 5 attempts. The minimum
  time between attempts is 30 seconds. After the last attempt, Attio removes the connection.
* If `connection-removed` runs for longer than the 30-second
  [server function timeout](https://docs.attio.com/sdk/server/server-functions.md#timeout), Attio stops it. Unlike a throw,
  a timeout is not retried: Attio removes the connection.
* In two cases, `connection-removed` cannot run, so Attio removes the connection without it and your
  cleanup does not happen:
  * The member who created the connection is not an active member of the workspace now.
    `connection-removed` runs as this member.
  * The OAuth access token of the connection is expired, and Attio cannot refresh it.
    `connection-removed` cannot use the connection.

In other situations, if `connection-removed` throws, Attio does not remove the connection. See
[`connection-removed`](https://docs.attio.com/sdk/server/events/connection-removed).

## Make cleanup safe to repeat

Attio can call `deactivate` and `connection-removed` more than one time. Make sure that
your cleanup code is safe to run again:

* If the third-party service returns "not found", do not throw or return an error. Possibly, the
  resource is already deleted.
* Store the IDs that you need for cleanup when you create the resource. One way to do this
  is with the [KV store](https://docs.attio.com/sdk/server/kv-store.md): in `activate`, you could save the external webhook
  ID under a key that includes the `uniqueActivationId`. Then in `deactivate`, you could read the
  ID, delete the webhook, and delete the key. If the key is not there, the cleanup is already done.
* For a webhook that your app registers for each connection, save the ID as the webhook handler's
  `externalWebhookId`.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.