> ## 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.

# Delegation Flow

> What happens when a user approves delegation and how to handle the webhook.

<Info>
  <strong>Server-only</strong><br />
  This page covers your <strong>server</strong> webhook handler. The client triggers delegation; your server verifies, decrypts, and stores materials.
</Info>

When a delegation is triggered, your endpoint receives a webhook named `wallet.delegation.created`. The delegated materials are in `data`.

```json theme={"system"}
{
  "messageId": "f44da9f0-a5b5-47f6-965f-f04af51c903e",
  "eventId": "2cf779a8-89da-486f-974e-2b77b738e4ac",
  "eventName": "wallet.delegation.created",
  "timestamp": "2025-10-01T15:13:26.348Z",
  "webhookId": "9a31fefc-64e4-4551-81da-1502eacc852d",
  "userId": "7eb7843b-2a4d-4f69-b95e-d219f0662fda",
  "environmentId": "53728749-1f19-4cab-becf-b88f952c3a3c",
  "environmentName": "sandbox",
  "data": {
    "chain": "EVM",
    "encryptedDelegatedShare": {
      "alg": "HYBRID-RSA-AES-256",
      "iv": "dzePdAUMQd6lWQngEXWPdQ",
      "ct": "pJIT5UU...XcWeYsXhygL2QbQcWZK6Rs5_CuiCDb_dHC_7P1tC...",
      "tag": "Yq8bpMU8huIx7UzUUUgI9Q",
      "ek": "uix2E6E...Keru7HWqeu7ktw"
    },
    "encryptedWalletApiKey": {
      "alg": "HYBRID-RSA-AES-256",
      "ct": "PzeliI...0kB9C0",
      "ek": "iWJgZQ...rxt",
      "iv": "RpC5nw1b4udgJqnC1p0evQ",
      "kid": "dynamic_rsa_lSuvWlCy",
      "tag": "-ZtmOG6gYTzS53wVMNK0Ig"
    },
    "publicKey": "0xd74ff800a3c6f66ecd217118aaa6fb1c916fa4e2",
    "userId": "7eb7843b-2a4d-4f69-b95e-d219f0662fda",
    "walletId": "25193936-3ecd-4c1b-84e6-9eabc82e53c2"
  }
}
```

### Verify → Decrypt → Store

1. Verify the webhook signature. See [Validate webhook signatures](/recipes/webhooks-signature-validation).
2. Decrypt `data.encryptedDelegatedShare` and `data.encryptedWalletApiKey`.
3. Store `userId`, `walletId`, and decrypted materials securely (e.g., envelope encryption, KMS, at-rest encryption).

<Info>
  <strong>Encryption fields</strong><br />
  `alg`: hybrid (RSA‑OAEP + AES‑256‑GCM); `iv`: AES IV; `ct`: ciphertext; `tag`: GCM tag; `ek`: encrypted content‑encryption key; `kid`: key identifier for rotation.
</Info>

### Example: Node (using Dynamic SDK)

If your server is Node.js, the easiest approach is to use our SDK helper which handles all the decryption logic for you:

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

```ts theme={"system"}
import { decryptDelegatedWebhookData } from '@dynamic-labs-wallet/node';

// In your webhook handler, after verifying the signature
const webhookData = req.body; // The webhook payload

const { decryptedDelegatedShare, decryptedWalletApiKey } =
  decryptDelegatedWebhookData({
    privateKeyPem: process.env.YOUR_PRIVATE_KEY, // Your RSA private key
    encryptedDelegatedKeyShare: webhookData.data.encryptedDelegatedShare,
    encryptedWalletApiKey: webhookData.data.encryptedWalletApiKey,
  });

// Now securely store these decrypted materials
// decryptedDelegatedShare: ServerKeyShare object
// decryptedWalletApiKey: string
```

### Manual Decryption (any language)

If you don't use Node.js, you can decrypt the materials yourself in any language that supports standard crypto primitives. The encryption scheme is hybrid RSA-OAEP + AES-256-GCM. Here's a reference implementation in TypeScript:

```ts theme={"system"}
import crypto from 'node:crypto';

type EncryptedPayload = {
  alg: string; iv: string; ct: string; tag: string; ek: string; kid?: string;
};

function decryptAesGcm(encryptedKey: Buffer, ivB64: string, ctB64: string, tagB64: string) {
  const iv = Buffer.from(ivB64, 'base64url');
  const ciphertext = Buffer.from(ctB64, 'base64url');
  const tag = Buffer.from(tagB64, 'base64url');
  const decipher = crypto.createDecipheriv('aes-256-gcm', encryptedKey, iv);
  decipher.setAuthTag(tag);
  const plaintext = Buffer.concat([decipher.update(ciphertext), decipher.final()]);
  return plaintext;
}

function rsaOaepDecryptEk(privateKeyPem: string, ekB64: string) {
  return crypto.privateDecrypt(
    { key: privateKeyPem, oaepHash: 'sha256', padding: crypto.constants.RSA_PKCS1_OAEP_PADDING },
    Buffer.from(ekB64, 'base64url')
  );
}

export function decryptMaterials(
  privateKeyPem: string,
  share: EncryptedPayload,
  apiKeyEnc: EncryptedPayload
) {
  const shareKey = rsaOaepDecryptEk(privateKeyPem, share.ek);
  const walletApiKeyKey = rsaOaepDecryptEk(privateKeyPem, apiKeyEnc.ek);

  const delegatedShare = decryptAesGcm(shareKey, share.iv, share.ct, share.tag);
  const walletApiKey = decryptAesGcm(walletApiKeyKey, apiKeyEnc.iv, apiKeyEnc.ct, apiKeyEnc.tag);

  return {
    delegatedShare: JSON.parse(delegatedShare.toString('utf8')),
    walletApiKey: walletApiKey.toString('utf8'),
  };
}
```

**Steps to replicate in your language of choice:**

1. Base64url-decode the `ek` field
2. RSA-OAEP decrypt (SHA-256) the decoded `ek` using your private key — this gives you the AES content-encryption key
3. Base64url-decode `iv`, `ct`, and `tag`
4. AES-256-GCM decrypt using the content-encryption key, IV, ciphertext, and auth tag
5. Repeat for both `encryptedDelegatedShare` and `encryptedWalletApiKey`

<Info>
  If a delivery fails, you can replay it from the dashboard. Use the `eventId` as an idempotency key.
</Info>

***

## Best Practices for Secure Storage

After decrypting the delegated materials, proper storage is critical. The `delegatedShare` and `walletApiKey`, in combination with your Dynamic developer API key, provide full signing authority and must be protected with defense-in-depth strategies.

### Recommended Storage Approaches

<AccordionGroup>
  <Accordion title="1. Envelope Encryption with Cloud KMS (Recommended)">
    Use a cloud Key Management Service to encrypt the decrypted materials before storing them in your database.

    **AWS KMS Example:**

    ```ts theme={"system"}
    import { KMSClient, EncryptCommand, DecryptCommand } from '@aws-sdk/client-kms';

    const kmsClient = new KMSClient({ region: 'us-east-1' });

    async function encryptWithKMS(plaintext: string, keyId: string) {
      const command = new EncryptCommand({
        KeyId: keyId,
        Plaintext: Buffer.from(plaintext),
      });
      const response = await kmsClient.send(command);
      return response.CiphertextBlob; // Store this in your database
    }

    async function decryptWithKMS(ciphertext: Uint8Array) {
      const command = new DecryptCommand({
        CiphertextBlob: ciphertext,
      });
      const response = await kmsClient.send(command);
      return Buffer.from(response.Plaintext).toString('utf8');
    }
    ```

    **Benefits:**

    * Centralized key management with automatic rotation
    * Hardware-backed security (FIPS 140-2 Level 3)
    * Audit logging of all encryption/decryption operations
    * Fine-grained IAM policies
  </Accordion>

  <Accordion title="2. Google Cloud KMS & Secret Manager">
    Similar to AWS KMS, but integrated with Google Cloud's ecosystem.

    ```ts theme={"system"}
    import { SecretManagerServiceClient } from '@google-cloud/secret-manager';
    import { KeyManagementServiceClient } from '@google-cloud/kms';

    const secretClient = new SecretManagerServiceClient();
    const kmsClient = new KeyManagementServiceClient();

    async function storeSecret(projectId: string, secretId: string, payload: string) {
      const [version] = await secretClient.addSecretVersion({
        parent: `projects/${projectId}/secrets/${secretId}`,
        payload: {
          data: Buffer.from(payload, 'utf8'),
        },
      });
      return version.name;
    }
    ```
  </Accordion>

  <Accordion title="3. Azure Key Vault">
    Microsoft Azure's managed secrets and key management service.

    ```ts theme={"system"}
    import { SecretClient } from '@azure/keyvault-secrets';
    import { DefaultAzureCredential } from '@azure/identity';

    const credential = new DefaultAzureCredential();
    const vaultUrl = `https://${vaultName}.vault.azure.net`;
    const client = new SecretClient(vaultUrl, credential);

    async function storeSecret(name: string, value: string) {
      await client.setSecret(name, value);
    }
    ```
  </Accordion>
</AccordionGroup>

### Security Requirements Checklist

Regardless of your storage method, follow these requirements:

* **Never log plaintext materials** — redact `delegatedShare` and `walletApiKey` from all logs, error messages, and monitoring
* **Encrypt at rest** — use AES-256-GCM or equivalent; ensure database/storage has encryption enabled
* **Encrypt in transit** — all communication must use TLS 1.3
* **Implement access controls** — restrict which services and roles can decrypt materials
* **Enable audit logging** — track all access to encrypted materials with timestamps and actor identity
* **Separate encryption keys** — don't reuse keys across environments (dev/staging/prod)
* **Use unique encryption per record** — generate new IVs for each encryption operation
* **Implement key rotation** — rotate encryption keys periodically (e.g., every 90 days)
* **Plan for key compromise** — document incident response for key material exposure
* **Secure deletion** — overwrite secrets in memory after use; use secure deletion for storage

### Storage Schema Example

```sql theme={"system"}
CREATE TABLE delegated_shares (
  id UUID PRIMARY KEY,
  user_id VARCHAR(255) NOT NULL,
  wallet_id VARCHAR(255) NOT NULL,
  chain VARCHAR(10) NOT NULL,

  -- Encrypted with KMS/Vault
  encrypted_delegated_share BYTEA NOT NULL,
  encrypted_wallet_api_key BYTEA NOT NULL,

  -- Metadata for key management
  encryption_key_id VARCHAR(255) NOT NULL,
  encryption_algorithm VARCHAR(50) DEFAULT 'AES-256-GCM',

  -- Audit fields
  created_at TIMESTAMP NOT NULL DEFAULT NOW(),
  last_accessed_at TIMESTAMP,
  access_count INTEGER DEFAULT 0,

  -- Indexes
  INDEX idx_user_wallet (user_id, wallet_id),
  INDEX idx_created_at (created_at)
);
```

### What NOT to Do

<Warning>
  * **Never store plaintext shares in databases, files, or environment variables**
  * **Never commit encryption keys or shares to version control**
  * **Never use the same encryption key across all users**
  * **Never skip signature verification before storing materials**
  * **Never rely solely on database encryption without application-level encryption**
  * **Never expose decrypted materials through APIs or logs**
</Warning>

***

<Card title="What's next?" icon="link" color="#4779FE" href="/kotlin/wallets/embedded-wallets/mpc/delegated-access/developer-actions">
  Learn how to use the delegated materials in Developer Actions.
</Card>
