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

# Offline Recovery

> Hold an encrypted recovery share so users can reconstruct their embedded wallet when Dynamic is offline.

## How does it work?

Offline recovery creates two additional encrypted key shares (a 2-of-2 pair), separate from the wallet's primary key shares:

* **Share 1.** Encrypted and stored in the user's Google Drive.
* **Share 2.** Encrypted and delivered to your configured webhook when you run an offline recovery reshare.

Share 1 and Share 2 can be combined for offline recovery.

## Configure offline recovery

To enable the feature follow the following steps:

1. Configure [Google Drive backup](https://docs.dynamic.xyz/docs/embedded-wallets/mpc/backup.md) for your environment.
2. In the Dynamic Console, go to **Wallets > Embedded Wallets > Backup & Recovery**.
3. Turn on **Developer Recovery Share**.
4. Configure the **Share Delivery** webhook URL and the RSA public key used to encrypt shares.

When offline recovery is enabled, users cannot use Google Drive as a regular cloud backup.

> [!NOTE]
>
> If [delegated access](https://docs.dynamic.xyz/docs/embedded-wallets/mpc/delegated-access/overview.md) is enabled, offline recovery uses the same share-delivery webhook URL and encryption key.

## Request a recovery share

Run an offline recovery reshare from the SDK for the user:

```typescript theme={"system"}
import { createWaasOfflineRecoveryShares } from '@dynamic-labs-sdk/client/waas';

// walletAccount: WalletAccount from the Dynamic client
await createWaasOfflineRecoveryShares({
  walletAccount,
});
```

This creates the recovery shares: Share 1 goes to Google Drive, and Share 2 is delivered to your webhook.

Step-up authentication is not required, but we strongly recommend requiring it before a recovery share is generated. Complete MFA, then pass the resulting `mfaToken`:

```typescript theme={"system"}
import { authenticateTotpMfaDevice } from '@dynamic-labs-sdk/client';
import { createWaasOfflineRecoveryShares } from '@dynamic-labs-sdk/client/waas';

// code: 6-digit TOTP from the user's authenticator app
const { mfaToken } = await authenticateTotpMfaDevice({
  code,
  createMfaTokenOptions: { singleUse: true },
});

await createWaasOfflineRecoveryShares({
  walletAccount,
  mfaToken,
});
```

For other verification methods (passkey, email OTP, and more), see [Step-up authentication](https://docs.dynamic.xyz/docs/auth/step-up-auth.md).

### Encrypt to an end user's key

By default, Share 2 is encrypted to the RSA public key configured on your environment, which means your backend can decrypt it. If you want the share to be decryptable only by the end user, pass a base64-encoded RSA public key (2048 bits or larger, PEM or raw key data) as `recoveryEncryptionPublicKey`, for example a key whose private key lives on the user's device:

```typescript theme={"system"}
import { createWaasOfflineRecoveryShares } from '@dynamic-labs-sdk/client/waas';

await createWaasOfflineRecoveryShares({
  walletAccount,
  mfaToken,
  // base64-encoded RSA public key registered for this user or wallet
  recoveryEncryptionPublicKey: 'LS0tLS1CRUdJTi...',
});
```

Generate a keypair wherever you want the private key to live, export the public key, and pass it base64-encoded. With Node's `crypto` module:

```typescript theme={"system"}
import { generateKeyPairSync } from 'crypto';

const { publicKey } = generateKeyPairSync('rsa', {
  modulusLength: 2048,
  publicKeyEncoding: { type: 'spki', format: 'pem' },
});

const recoveryEncryptionPublicKey = Buffer.from(publicKey).toString('base64');
```

Keep the private key with the end user or wherever you intend recovery to be possible; only the matching private key can decrypt the delivered share.

When omitted, the environment's configured key is used. The webhook event is identical either way: it carries the encrypted share and the `kid` of the key it was encrypted to (for caller-supplied keys, a SHA-256 fingerprint of the key bytes).

Your webhook receives a `wallet.offlineRecovery.created` event containing the encrypted Share 2. Store Share 2 securely.

## Recovery

To recover, the user needs both recovery shares:

1. **Share 2** from your webhook.
2. **Share 1** from their Google Drive.

With both shares, recovery is a local cryptographic operation. Dynamic is not involved.

Install the browser wallet package, then call `offlineExportKey` with the two decrypted shares:

```bash theme={"system"}
npm install @dynamic-labs-wallet/browser
```

```typescript theme={"system"}
import { DynamicWalletClient } from '@dynamic-labs-wallet/browser';

// environmentId: your Dynamic environment ID
// share1, share2: decrypted key shares ({ pubkey, secretShare })
const client = new DynamicWalletClient({ environmentId });

const { derivedPrivateKey } = await client.offlineExportKey({
  chainName: 'EVM',
  keyShares: [share1, share2],
});
```

For a full implementation, including decrypting Share 1 and Share 2, see the [example recovery tool](https://github.com/dynamic-labs-oss/offline-recovery).


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