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

# Data protection

> Handle customers/redact, shop/redact and customers/data_request within 30 days, and know which installs receive each notice.

A merchant can delete a customer record, delete their store, or ask for a copy of what the store
holds for one shopper. Salesive forwards each of those as a **mandatory webhook** to the apps
installed on the store, so your app can erase or return what it holds.

The notices carry identifiers only: a customer id or a shop id, never the record itself. They cannot
be opted out of with `webhookTopics`, and `customers/redact` and `shop/redact` are sent whatever
scopes the installation currently holds. If your app stores customer data, you must handle them.

## The three notices

| Topic | Fires when | Who receives it | `resourceId` |
| - | - | - | - |
| `customers/redact` | A customer record is deleted from the store. | Every active installation of the shop with a webhook URL, whatever its scopes. | The customer id. |
| `customers/data_request` | The merchant asks for a copy of what the store holds for one customer (`POST /api/v1/customers/:id/data-request`). | Active installations holding `READ_CUSTOMERS`, `WRITE_CUSTOMERS` or `READ_CUSTOMER_ANALYTICS`. | The customer id. |
| `shop/redact` | The store is deleted, or its owner deletes their account and the store is taken offline. | Every active installation of the shop with a webhook URL, whatever its scopes. | The shop id. |

`shop/redact` is shop wide: one envelope per installation, with the shop as both the routing key and
the record being erased. The two customer topics carry the customer id in `resourceId`.

## The 30 day expectation

Complete each notice within **30 days** of the delivery.

* **`customers/redact`**: erase every record you hold for that customer id, including backups,
  caches and derived data, and stop using it. Keep only what the law requires you to keep, and keep
  that quarantined rather than in active use.
* **`shop/redact`**: erase everything you hold for the shop and its customers, and deactivate your
  copy of the store.
* **`customers/data_request`**: compile what you hold for that customer and give it to the merchant,
  or tell them you hold nothing.

Salesive does not enforce the deadline for you and does not retry for 30 days. The notice is
delivered with the normal webhook retry ladder, so make sure your handler is idempotent and keeps a
record of which notices it has completed. Put the 30 day window in your own privacy policy too.

## What a notice looks like

A `customers/redact` delivery:

```json theme={null}
{
  "id": "8f1c2e3a-...-9b0d",
  "topic": "customers/redact",
  "resource": "customers",
  "action": "redact",
  "resourceId": "66b1f0a3c2d4e5f6a7b8c9d0",
  "shopId": "6680aabbccddeeff00112200",
  "occurredAt": "2026-11-07T10:00:00.000Z"
}
```

A `shop/redact` delivery uses `"resource": "shop"`, `"action": "redact"` and the shop id as
`resourceId`. A `customers/data_request` delivery uses `"action": "data_request"`.

There are no customer fields in any of them. Use `shopId` and `resourceId` to find your own records.
Do not call the Salesive API to fetch the customer: the record may already be gone, and the notice is
the authority.

## Handle a notice

1. Verify the signature and reject a stale timestamp. See
   [Webhooks](https://docs.salesive.com/apps-api/webhooks.md#verify-the-signature).
2. Dedupe on `id`; delivery is at-least-once.
3. Route on `topic`.
4. Look the record up by `shopId` and `resourceId` in your own storage.
5. Erase or export it, and record when you completed it.
6. Acknowledge with a `2xx` quickly. Do the work asynchronously if it is slow.

```js theme={null}
app.post("/webhooks/salesive", express.raw({ type: "application/json" }), async (req, res) => {
  if (!verifySalesiveSignature(req)) return res.status(401).send("invalid signature");

  const event = JSON.parse(req.body.toString("utf8"));

  switch (event.topic) {
    case "customers/redact":
      await eraseCustomer(event.shopId, event.resourceId);
      break;
    case "shop/redact":
      await eraseShop(event.shopId);
      break;
    case "customers/data_request":
      await queueCustomerExport(event.shopId, event.resourceId);
      break;
    default:
      break;
  }

  res.sendStatus(200);
});
```

> [!NOTE]
>
> A webhook has no reply channel. If the merchant asked for a copy of the data, deliver it through
> your own app UI or email, not by answering the webhook.

## Delivery details

* One delivery per installation, at the URL and signing secret that installation resolves to. A
  production install uses its pinned [release](https://docs.salesive.com/apps/releases.md); a development install uses
  `devConfig`.
* Mandatory: these topics are delivered even when they are not in `webhookTopics`.
* `customers/redact` and `shop/redact` need no scope. `customers/data_request` needs one of the three
  customer scopes.
* If your app has no webhook URL, nothing is delivered. Set one before you store any customer data.
* Every attempt appears in the [delivery log](https://docs.salesive.com/apps-api/webhook-deliveries.md) for 14 days.

## Also do this

* Store only the customer data your app actually needs, and know where every copy lives.
* Encrypt customer data at rest.
* Keep a data map so a redaction is complete rather than best effort.
* Treat a `customers/data_request` as an export task, not as permission to read more of the store.

## Next steps

> [card] [Webhooks](https://docs.salesive.com/apps-api/webhooks.md)
>
> Signature verification, topics and retries.

> [card] [Scopes & permissions](https://docs.salesive.com/apps/scopes-permissions.md)
>
> Which customer scopes exist and how approval works.


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