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

# Encrypted Payloads (JWE)

> Encrypt your API request bodies end-to-end with JWE before sending them to AutoSend. Encryption is fully opt-in, per request.

export const APP_PATHS = {
  home: '/',
  quickstart: '/quickstart',
  domainConfiguration: '/domain',
  apiReference: '/api-reference',
  sendEmail: '/api-reference/mails/send',
  bulkSendEmail: '/api-reference/mails/bulk',
  upsertContactApiRef: '/api-reference/contacts/upsert-contact',
  transactional: '/transactional-emails',
  emailActivity: '/transactional-emails/email-activity',
  emailTemplates: '/transactional-emails/email-templates',
  sendingEmail: '/quickstart/email-using-api',
  transactionalTroubleshooting: '/transactional-emails/troubleshooting',
  marketing: '/marketing-emails',
  campaigns: '/marketing-emails/campaigns',
  contacts: '/marketing-emails/contacts',
  contactsIntroduction: '/marketing-emails/contacts/introduction',
  contactsImportCsv: '/marketing-emails/contacts/import-csv',
  contactsLists: '/marketing-emails/contacts/lists',
  contactsSegments: '/marketing-emails/contacts/segments',
  contactsCustomFields: '/marketing-emails/contacts/contact-properties',
  contactsContactProperties: '/marketing-emails/contacts/contact-properties',
  createContactPropertyApiRef: '/api-reference/contact-properties/create',
  listContactPropertiesApiRef: '/api-reference/contact-properties/list',
  getContactPropertyApiRef: '/api-reference/contact-properties/get-by-name',
  deleteContactPropertyApiRef: '/api-reference/contact-properties/delete',
  sender: '/marketing-emails/sender',
  unsubscribeGroups: '/others/unsubscribe-groups',
  webhookIntroduction: '/others/webhooks/introduction',
  webhookEventType: '/others/webhooks/event-type',
  webhookRetries: '/others/webhooks/retries',
  webhookVerifyRequests: '/others/webhooks/verify-requests',
  dynamicTemplates: '/dynamic-templates',
  guides: '/guides',
  sitemap: '/sitemap.xml',
  team: '/others/team',
  automations: '/automations',
  events: '/automations/events',
  sendEventApi: '/api-reference/events/send-event',
  smtpIntroduction: '/quickstart/smtp',
  betterAuth: '/guides/better-auth',
  convexGuide: '/guides/convex',
  templateVariables: '/transactional-emails/variables',
  suppressions: '/others/suppressions',
  rateLimit: '/api-reference/rate-limit',
  nodejsSdk: '/sdk/nodejs',
  smtpIntegrationGuides: '/guides/smtp',
  apiKeys: '/api-keys',
  encryptedPayloads: '/others/encrypted-payloads',
  apiReferenceIntroduction: '/api-reference/introduction',
  lovableGuide: '/ai/integrations/lovable',
  aiIntroduction: '/ai/introduction',
  aiSkills: '/ai/skills',
  aiMcpServer: '/ai/mcp-server',
  aiLovable: '/ai/integrations/lovable',
  aiBolt: '/ai/integrations/bolt',
  aiV0: '/ai/integrations/v0',
  aiReplit: '/ai/integrations/replit',
  mcpClaude: '/ai/mcp-clients/claude',
  mcpCursor: '/ai/mcp-clients/cursor',
  mcpCopilot: '/ai/mcp-clients/copilot',
  mcpWindsurf: '/ai/mcp-clients/windsurf',
  mcpCodex: '/ai/mcp-clients/codex',
  mcpAntigravity: '/ai/mcp-clients/antigravity',
  mcpChatgpt: '/ai/mcp-clients/chatgpt',
  mcpRaycast: '/ai/mcp-clients/raycast',
  domainWarmup: '/marketing-emails/domain-warmup',
  projects: '/projects',
  createAutomationApi: '/api-reference/automations/create-automation',
  migrationSendgrid: '/migration/sendgrid',
  migrationResend: '/migration/resend',
  auth0CustomAction: '/guides/auth0-custom-action',
  accountBilling: '/others/account/billing',
  accountUsage: '/others/account/usage',
  inboundIntroduction: '/inbound/introduction',
  listInboundMessagesApi: '/api-reference/inbound-emails/list-messages',
  getInboundMessageApi: '/api-reference/inbound-emails/get-message',
  downloadInboundAttachmentApi: '/api-reference/inbound-emails/download-attachment',
  replyToInboundMessageApi: '/api-reference/inbound-emails/reply-to-message',
  wikiDailySendingLimit: '/wiki/daily-sending-limit'
};

AutoSend optionally accepts **JWE-encrypted request bodies** on the public API. You encrypt your JSON payload with AutoSend's public key, and the backend decrypts it transparently. Your business logic and the API response are unchanged.

Plaintext requests keep working exactly as before, so encryption is fully opt-in, per request.

<Note>
  Transport is already protected by HTTPS/TLS. JWE adds an **application-layer** of protection so the payload stays encrypted end-to-end (for example, through logs, proxies, or intermediaries) until it reaches AutoSend.
</Note>

***

## How it works

<Steps>
  <Step title="Fetch the public key" titleSize="h3">
    Get AutoSend's public key from the JWKS endpoint and cache it. The recommended (active) key is always listed first.
  </Step>

  <Step title="Encrypt your payload" titleSize="h3">
    Encrypt the complete JSON body into a JWE compact string using `RSA-OAEP-256` for key management and `A256GCM` for content encryption.
  </Step>

  <Step title="Send the encrypted request" titleSize="h3">
    POST `{ "encryptedData": "<JWE>" }` to any public endpoint with the `X-Payload-Encryption: jwe` and `X-Key-Id` headers.
  </Step>
</Steps>

***

## Encryption standard

| Field                      | Value          |
| -------------------------- | -------------- |
| Key management (`alg`)     | `RSA-OAEP-256` |
| Content encryption (`enc`) | `A256GCM`      |
| Serialization              | JWE Compact    |
| Key size                   | RSA 2048-bit   |

***

## Public key endpoint (JWKS)

Fetch AutoSend's public key from the JWKS endpoint:

```http theme={null}
GET https://api.autosend.com/v1/jwks.json
```

```json theme={null}
{
	"keys": [
		{
			"kty": "RSA",
			"n": "…",
			"e": "AQAB",
			"kid": "key_2026_06",
			"alg": "RSA-OAEP-256",
			"use": "enc"
		}
	]
}
```

<Tip>
  Fetch the JWKS periodically and cache it. Use the `kid` of the key you encrypt with. AutoSend supports multiple keys at once so keys can rotate without breaking in-flight requests.
</Tip>

***

## Node.js example

This example fetches the public key, encrypts a contact payload, and calls the contacts API. It uses the [`jose`](https://github.com/panva/jose) library.

<CodeGroup>
  ```bash Install theme={null}
  npm install jose
  ```

  ```js encrypt-and-send.js expandable theme={null}
  import { importJWK, CompactEncrypt } from 'jose';

  const API_BASE = 'https://api.autosend.com';
  const API_KEY = 'AS_xxx.yyy'; // your AutoSend API key

  const JWE_ALG = 'RSA-OAEP-256';
  const JWE_ENC = 'A256GCM';

  // 1. Fetch AutoSend's public key from the JWKS endpoint.
  async function fetchPublicKey() {
  	const res = await fetch(`${API_BASE}/v1/jwks.json`);
  	if (!res.ok) throw new Error(`Failed to fetch JWKS: ${res.status}`);

  	const { keys } = await res.json();
  	const jwk = keys[0]; // the active key is listed first

  	const publicKey = await importJWK(jwk, JWE_ALG);
  	return { publicKey, kid: jwk.kid };
  }

  // 2. Encrypt the JSON payload into a JWE compact string.
  async function encryptPayload(payload, publicKey, kid) {
  	const data = new TextEncoder().encode(JSON.stringify(payload));
  	return new CompactEncrypt(data)
  		.setProtectedHeader({ alg: JWE_ALG, enc: JWE_ENC, kid })
  		.encrypt(publicKey);
  }

  // 3. Send the encrypted request to the contacts API.
  async function createEncryptedContact(contact) {
  	const { publicKey, kid } = await fetchPublicKey();
  	const encryptedData = await encryptPayload(contact, publicKey, kid);

  	const res = await fetch(`${API_BASE}/v1/contacts/email`, {
  		method: 'POST',
  		headers: {
  			'Content-Type': 'application/json',
  			Authorization: `Bearer ${API_KEY}`,
  			'X-Payload-Encryption': 'jwe',
  			'X-Key-Id': kid,
  		},
  		body: JSON.stringify({ encryptedData }),
  	});

  	return res.json();
  }

  // Usage
  const result = await createEncryptedContact({
  	email: 'john@example.com',
  	firstName: 'John',
  	lastName: 'Doe',
  	customFields: {
  		isVerified: false,
  		industry: 'Telecommunications',
  	},
  });

  console.log(result);
  ```
</CodeGroup>

<Info>
  Any language with a JWE library works. Equivalent libraries exist for Python
  (`jwcrypto` / `python-jose`), Go, Java, Ruby, and more. Always use `alg =
    	RSA-OAEP-256` and `enc = A256GCM`.
</Info>

***

## Request format

Send the JWE as `encryptedData`, with the encryption headers:

<CodeGroup>
  ```http Encrypted theme={null}
  POST /v1/contacts/email
  Authorization: Bearer AS_xxx.yyy
  Content-Type: application/json
  X-Payload-Encryption: jwe
  X-Key-Id: key_2026_06

  { "encryptedData": "eyJhbGciOiJSU0EtT0FFUC0yNTYi..." }
  ```

  ```http Plaintext (default) theme={null}
  POST /v1/contacts/email
  Authorization: Bearer AS_xxx.yyy
  Content-Type: application/json

  { "email": "john@example.com", "firstName": "John" }
  ```
</CodeGroup>

<ParamField path="X-Payload-Encryption" type="string">
  Set to `jwe` for encrypted requests. Omit (or use `none`) for plaintext.
</ParamField>

<ParamField path="X-Key-Id" type="string">
  The `kid` of the public key you encrypted with. If omitted, AutoSend reads the `kid` from the JWE protected header.
</ParamField>

<ParamField path="encryptedData" type="string" required>
  The JWE compact string containing your complete JSON payload.
</ParamField>

***

## Works with any public endpoint

The encrypted request format is the same for every `/v1` endpoint. Just put the JWE inside `{ "encryptedData": "…" }` and add the headers. For example, to send an email:

```http theme={null}
POST /v1/mails/send
Authorization: Bearer AS_xxx.yyy
Content-Type: application/json
X-Payload-Encryption: jwe
X-Key-Id: key_2026_06

{ "encryptedData": "eyJhbGciOiJSU0EtT0FFUC0yNTYi..." }
```

***

## Error handling

If the encrypted payload is malformed, the key id is unknown, or decryption fails, the API responds with **HTTP 400**:

```json theme={null}
{
	"success": false,
	"error": {
		"message": "Invalid encrypted payload",
		"code": "INVALID_ENCRYPTED_PAYLOAD",
		"status": 400
	}
}
```

<AccordionGroup>
  <Accordion title="Malformed JWE">
    The `encryptedData` value is not a valid JWE compact string. Re-encrypt the payload with the public key from the JWKS endpoint.
  </Accordion>

  <Accordion title="Unknown or missing key id">
    The `kid` doesn't match any active AutoSend key. Refresh the JWKS and use the `kid` from the returned key.
  </Accordion>

  <Accordion title="Decryption failure">
    The payload was encrypted with the wrong key or a different algorithm. Use `RSA-OAEP-256` + `A256GCM` and AutoSend's current public key.
  </Accordion>
</AccordionGroup>

***

## FAQ

<AccordionGroup>
  <Accordion title="Is encryption required?">
    No. It's optional and per request. Endpoints accept plaintext bodies exactly as before. Only requests with the `jwe` header or an `encryptedData` field are decrypted.
  </Accordion>

  <Accordion title="How often should I fetch the public key?">
    Cache it and refresh periodically (for example, daily or weekly). AutoSend supports key rotation, so always encrypt with the `kid` from the latest JWKS.
  </Accordion>

  <Accordion title="Which languages are supported?">
    Any language with a JWE library. The example uses Node.js with `jose`; equivalent libraries exist for Python (`jwcrypto` / `python-jose`), Go, Java, Ruby, and more. Use `alg = RSA-OAEP-256` and `enc = A256GCM`.
  </Accordion>

  <Accordion title="Do I still need HTTPS?">
    Yes. JWE complements transport security, it does not replace it. Always call AutoSend over HTTPS and add JWE on top when you need application-layer encryption.
  </Accordion>
</AccordionGroup>

***

## Related Resources

<Columns cols={2}>
  <Card title="API Keys" icon="https://mintcdn.com/autosend-13920f5c/nx_wYfWx3qeZwg1C/icons/api-key.svg?fit=max&auto=format&n=nx_wYfWx3qeZwg1C&q=85&s=901e030c43bc15e040cb524638069800" href={APP_PATHS.apiKeys} width="24" height="24" data-path="icons/api-key.svg">
    Create and manage the API keys you authenticate requests with.
  </Card>

  <Card title="API Reference" icon="https://mintcdn.com/autosend-13920f5c/nx_wYfWx3qeZwg1C/icons/api.svg?fit=max&auto=format&n=nx_wYfWx3qeZwg1C&q=85&s=a257e726f0f001df70664b740dcd5af6" href={APP_PATHS.apiReferenceIntroduction} width="24" height="24" data-path="icons/api.svg">
    Explore every public endpoint you can send encrypted payloads to.
  </Card>

  <Card title="Upsert Contact" icon="https://mintcdn.com/autosend-13920f5c/nx_wYfWx3qeZwg1C/icons/contacts.svg?fit=max&auto=format&n=nx_wYfWx3qeZwg1C&q=85&s=93b686fb3cb253812d2ab70168336374" href={APP_PATHS.upsertContactApiRef} width="24" height="24" data-path="icons/contacts.svg">
    The contacts endpoint used in the encryption example above.
  </Card>

  <Card title="Send Email" icon="https://mintcdn.com/autosend-13920f5c/nx_wYfWx3qeZwg1C/icons/email-activity.svg?fit=max&auto=format&n=nx_wYfWx3qeZwg1C&q=85&s=2ecad7369f217ee7d03c3d8dfdd36d22" href={APP_PATHS.sendEmail} width="24" height="24" data-path="icons/email-activity.svg">
    Send transactional emails, with or without an encrypted body.
  </Card>
</Columns>


## Related topics

- [Migrate from SendGrid to AutoSend](/migration/sendgrid.md)
- [Migrate from Resend to AutoSend](/migration/resend.md)
- [How to send email with AutoSend SMTP](/quickstart/smtp.md)
- [Verify Webhook Requests](/others/webhooks/verify-requests.md)
- [Changelog](/changelog.md)
