Encrypt Your Requests
Wrap request bodies in an AES-256-GCM envelope derived from your client secret key.
Field-level request encryption is optional and off unless you turn it on. While it is on, every request body must arrive as an encrypted envelope instead of plain JSON. Responses always come back as normal, unencrypted JSON.
GET and HEAD requests carry no body, so they are sent normally whether encryption is on or off.
If you have not enabled it, send request bodies as plain JSON exactly as the guides show, and skip this page.
Enabling encryption on your credential
Enable encryption from Settings › Developer API in Duplo Dashboard, the same menu you generate credentials from. You can switch it on or off whenever you like without rotating the key, because the client secret key is issued alongside the API key either way.
Encrypting a body needs that client secret key, which is shown once, at creation time. If you no longer have it, the only way to get a new one is to regenerate your credentials, which invalidates your current API key for that mode. Your encryption setting and IP allowlist carry over to the new key, and the other mode's key is untouched.
Turn it on in the right order
The setting takes effect immediately. Ship the encryption code first and
enable the toggle once it is deployed, or in-flight plain JSON requests start
failing with 400.
Building the encrypted envelope
- Derive a 32-byte AES-256 key as
SHA-256(clientSecretKey). - Encrypt your JSON request body with AES-256-GCM using a random 12-byte IV.
- Build the payload as
iv (12 bytes) + ciphertext + authTag (16 bytes), base64-encoded. - Send it as
{ "client": "<base64 payload>" }.
Generate a fresh IV for every request. The Authorization header and Content-Type: application/json are unchanged; only the body is wrapped.
The samples below use a placeholder endpoint and payload, because the Duplo Dashboard endpoints documented today are read-only GETs that carry no body. Substitute the body-bearing endpoint your integration calls.
const crypto = require("crypto");
const axios = require("axios");
const encryptBody = (body, clientSecretKey) => {
const key = crypto.createHash("sha256").update(clientSecretKey).digest();
const iv = crypto.randomBytes(12);
const cipher = crypto.createCipheriv("aes-256-gcm", key, iv);
const ciphertext = Buffer.concat([
cipher.update(JSON.stringify(body), "utf8"),
cipher.final(),
]);
const payload = Buffer.concat([iv, ciphertext, cipher.getAuthTag()]);
return { client: payload.toString("base64") };
};
const sendEncryptedRequest = async () => {
const response = await axios.post(
"https://dashboard.tryduplo.com/api/<endpoint>",
encryptBody({ field: "value" }, "<your-client-secret-key>"),
{
headers: {
Authorization: "Bearer <your-api-key>",
"Content-Type": "application/json",
},
},
);
console.log(response.data);
};
sendEncryptedRequest();Handling encryption errors
If encryption is enabled on your key but the envelope is missing or fails to decrypt, you get a 400 with one of two messages:
| Message | What it means |
|---|---|
Encrypted request body is required | Encryption is on, but the body had no client field, or it was empty. |
Unable to decrypt request body | The envelope did not decrypt: it is malformed, truncated, or encrypted with the wrong key. |
Decrypted request body is invalid | The envelope decrypted, but the plaintext inside was not a JSON object. |
Encryption is misconfigured for this key | Encryption is on for your credential but the key material behind it cannot be used. Regenerate your credentials, or contact support if it persists. |
The reverse mistake has its own message. Sending a { "client": ... } envelope to a key that does not have encryption enabled returns 400 with This request includes a client field, but encryption is not enabled for this API key. Send the plain (unencrypted) request body instead. If you see that, the envelope is fine and the toggle is off.
Because responses stay unencrypted, you can read the error straight off the wire while you are getting the envelope right.
Related guides
How is this guide?
Last updated on
Restrict Your API Key
Narrow what your key can do by restricting the IP addresses it accepts and requiring encrypted request bodies, and know the rate limit it runs under.
Read Your Expenses
List the expenses recorded for your business, filter them by status or date, and fetch a single expense by its id.