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

# Encryption

> How to RSA-encrypt request payloads before sending them to the Checkout API

Every order endpoint takes its request body as a single encrypted string rather than plain
JSON. You build the payload as JSON, encrypt it with your RSA public encryption key, and send
the result as `data`:

```json theme={null}
{
  "data": "mqLJ+iUTxAG/y6O7Xe8ZyxT8VFVEDQvc99UEp3mk8ntPnphZ7/zaGwovPypaG3ml..."
}
```

<Warning>
  The encryption key is **not** the same thing as your API key. Your `api-key` header
  authenticates the request; the RSA public encryption key encrypts the body. Both are issued
  during onboarding.
</Warning>

## Which endpoints are encrypted

| Encrypted (`{"data": "..."}`)                              | Plain JSON                                                        |
| ---------------------------------------------------------- | ----------------------------------------------------------------- |
| [Create an order](/api-reference/orders/create-order)      | [Verify an order](/api-reference/verification/verify-order)       |
| [Pay an order](/api-reference/orders/pay-order)            | [Tokenized charge](/api-reference/tokenized/tokenized-charge)     |
| [Get order status](/api-reference/orders/get-order-status) | [Create a refund](/api-reference/refunds/create-refund)           |
| [Get order fee](/api-reference/orders/get-order-fee)       | [Get payment keys](/api-reference/payment-links/get-payment-keys) |
| [Save a card](/api-reference/orders/save-card)             |                                                                   |
| [Track an event](/api-reference/orders/track-event)        |                                                                   |

`GET` endpoints — [List banks](/api-reference/banks/list-banks),
[Fetch a payment link](/api-reference/payment-links/fetch-payment-link),
[List refunds](/api-reference/refunds/list-refunds),
[List chargebacks](/api-reference/refunds/list-chargebacks) and
[Ping](/api-reference/utility/ping) — take no body at all.

<Note>
  [Track an event](/api-reference/orders/track-event) is the one endpoint whose envelope key is
  capitalised: `{"Data": "..."}`, not `{"data": "..."}`.
</Note>

## The key format

Your public encryption key arrives as a single base64 string. Decoded, it looks like this:

```
8192!<RSAKeyValue><Modulus>qFtkrP7iSvVjBC1LCtcHROMBJqz9/G0ODTxVduZv4/THbtht...</Modulus><Exponent>AQAB</Exponent></RSAKeyValue>
```

Three things to notice:

| Part         | Meaning                                                                            |
| ------------ | ---------------------------------------------------------------------------------- |
| `8192`       | Key size in bits, followed by a `!` separator. Split on `!` and discard this half. |
| `<Modulus>`  | The RSA modulus, base64-encoded.                                                   |
| `<Exponent>` | The RSA public exponent, base64-encoded. `AQAB` is 65537.                          |

So the procedure is always: base64-decode the key → split on `!` → take the second half → parse
the XML → base64-decode `Modulus` and `Exponent` → build an RSA public key from them.

## The algorithm

| Setting   | Value                           |
| --------- | ------------------------------- |
| Algorithm | RSA                             |
| Mode      | ECB                             |
| Padding   | **PKCS#1 v1.5**                 |
| Input     | The JSON payload, UTF-8 encoded |
| Output    | Base64 string                   |

<Warning>
  PKCS#1 v1.5 — not OAEP. Browser `SubtleCrypto` only implements `RSA-OAEP` for encryption, so
  a pure WebCrypto implementation is not possible; use a library (or do it server-side, which
  you should be doing anyway to keep the key off the client).
</Warning>

## Code

<Tabs>
  <Tab title="JavaScript">
    Requires [`node-forge`](https://www.npmjs.com/package/node-forge).

    ```javascript encrypt.js theme={null}
    import rsa from 'node-forge'

    const BigInteger = rsa.jsbn.BigInteger
    const parser = new DOMParser()

    function parseBigInteger(b64) {
      return new BigInteger(
        rsa.util.createBuffer(rsa.util.decode64(b64)).toHex(),
        16,
      )
    }

    export default function encryptForge(data, rsa_pub_key) {
      let rsaKeyValue = atob(rsa_pub_key)
      rsaKeyValue = rsaKeyValue.split('!')[1]

      const xmlDoc = parser.parseFromString(rsaKeyValue, 'text/xml')
      const modulus = xmlDoc.getElementsByTagName('Modulus')[0].innerHTML
      const exponent = xmlDoc.getElementsByTagName('Exponent')[0].innerHTML

      const pubKey = rsa.pki.setRsaPublicKey(
        parseBigInteger(modulus),
        parseBigInteger(exponent),
      )

      const encryptText = pubKey.encrypt(rsa.util.encodeUtf8(data))
      return btoa(encryptText)
    }
    ```
  </Tab>

  <Tab title="Python">
    Requires [`pycryptodome`](https://pypi.org/project/pycryptodome/).

    ```python encrypt.py theme={null}
    import base64
    import xml.etree.ElementTree as ET
    from Crypto.PublicKey import RSA
    from Crypto.Cipher import PKCS1_v1_5


    def get_xml_component(xmlstring, field):
        root = ET.fromstring(xmlstring)
        elements = root.findall(field)
        return elements[0].text if elements else ""


    def encrypt(data, public_xml):
        if not data:
            raise Exception("Data sent for encryption is empty")

        decoded_string = base64.b64decode(public_xml).decode("utf-8")
        public_xml_key = decoded_string.split("!")[1]

        modulus = base64.b64decode(get_xml_component(public_xml_key, "Modulus"))
        exponent = base64.b64decode(get_xml_component(public_xml_key, "Exponent"))

        key = RSA.construct((
            int.from_bytes(modulus, byteorder="big"),
            int.from_bytes(exponent, byteorder="big"),
        ))

        cipher = PKCS1_v1_5.new(key)
        return base64.b64encode(cipher.encrypt(data.encode("utf-8"))).decode()
    ```
  </Tab>

  <Tab title="PHP">
    Requires [`phpseclib3`](https://phpseclib.com/).

    ```php encrypt.php theme={null}
    <?php
    require 'vendor/autoload.php';

    use phpseclib3\Crypt\RSA;
    use phpseclib3\Math\BigInteger;

    function getXmlComponent($xmlstring, $field)
    {
        $xml = new SimpleXMLElement($xmlstring);
        $result = $xml->xpath("//$field");
        return ($result && count($result) > 0) ? (string) $result[0] : "";
    }

    function encryptData($data, $public_xml)
    {
        if (!$data) {
            throw new Exception("Data sent for encryption is empty");
        }

        $decoded_string = base64_decode($public_xml);
        $public_xml_key = explode('!', $decoded_string)[1];

        $modulus  = base64_decode(getXmlComponent($public_xml_key, "Modulus"));
        $exponent = base64_decode(getXmlComponent($public_xml_key, "Exponent"));

        $rsa = RSA::loadFormat('raw', [
            'n' => new BigInteger($modulus, 256),
            'e' => new BigInteger($exponent, 256),
        ])->withPadding(RSA::ENCRYPTION_PKCS1);

        return base64_encode($rsa->encrypt($data));
    }
    ```
  </Tab>

  <Tab title="Java">
    Uses only the JDK.

    ```java EncryptionHelper.java theme={null}
    import org.w3c.dom.Document;
    import org.w3c.dom.Element;
    import org.w3c.dom.NodeList;

    import javax.crypto.Cipher;
    import javax.xml.parsers.DocumentBuilder;
    import javax.xml.parsers.DocumentBuilderFactory;
    import java.security.KeyFactory;
    import java.security.PublicKey;
    import java.security.spec.RSAPublicKeySpec;
    import java.util.Base64;

    public class EncryptionHelper {

        public static String getXmlComponent(String xmlstring, String field) throws Exception {
            DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
            DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
            Document doc = dBuilder.parse(
                new java.io.ByteArrayInputStream(xmlstring.getBytes("UTF-8")));
            doc.getDocumentElement().normalize();

            NodeList nodes = doc.getElementsByTagName(field);
            if (nodes.getLength() == 0) return "";
            return ((Element) nodes.item(0)).getTextContent();
        }

        public static String encrypt(String data, String publicXml) throws Exception {
            if (data == null || data.isEmpty()) {
                throw new Exception("Data sent for encryption is empty");
            }

            String decodedString = new String(Base64.getDecoder().decode(publicXml));
            String publicXmlKey = decodedString.split("!")[1];

            byte[] modulusBytes  = Base64.getDecoder().decode(getXmlComponent(publicXmlKey, "Modulus"));
            byte[] exponentBytes = Base64.getDecoder().decode(getXmlComponent(publicXmlKey, "Exponent"));

            RSAPublicKeySpec keySpec = new RSAPublicKeySpec(
                new java.math.BigInteger(1, modulusBytes),
                new java.math.BigInteger(1, exponentBytes));

            PublicKey publicKey = KeyFactory.getInstance("RSA").generatePublic(keySpec);

            Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
            cipher.init(Cipher.ENCRYPT_MODE, publicKey);

            return Base64.getEncoder().encodeToString(cipher.doFinal(data.getBytes("UTF-8")));
        }
    }
    ```
  </Tab>
</Tabs>

## The sandbox encryption helper

While you are building, sandbox exposes an endpoint that encrypts a payload for you, so you can
exercise the API before your own encryption code works.

```
POST https://api-v4.reeple.ai/charge/data/encrypt
```

Send the plaintext JSON payload as the body, with your public key in the `api-key` header:

```bash theme={null}
curl -X POST https://api-v4.reeple.ai/charge/data/encrypt \
  -H "Content-Type: application/json" \
  -H "api-key: YOUR_PUBLIC_KEY" \
  -d '{ "reference": "your-order-reference" }'
```

```json theme={null}
{
  "data": "ASK7UxU45861CgMkQGlcUbQgLcWBpuw4sUEnd4+xWfD5VnEK6rT07cBb32VLcHK9...",
  "message": "Operation successful"
}
```

Take `data` from the response and send it as the `data` field of the real endpoint.

<Warning>
  **This endpoint does not exist in production.** It is a development aid only. Ship your own
  encryption before going live, and never route live payloads through it.
</Warning>

<Note>
  Older integrations may reference `/data/encrypt` or `/payment/data/encrypt`. Both are
  superseded — use `/charge/data/encrypt`.
</Note>

## Troubleshooting

<AccordionGroup>
  <Accordion title="400 — Something went wrong while trying to decrypt your payload">
    ```json theme={null}
    {
      "status": "failed",
      "statusCode": "400",
      "message": "Something went wrong while trying to decrypt your payload, please try again or contact support"
    }
    ```

    The `data` string could not be decrypted. Usual causes: the wrong padding (OAEP instead of
    PKCS#1 v1.5), a key that belongs to a different environment, encrypting the raw bytes
    instead of base64-encoding the ciphertext, or a payload longer than the key can encrypt in
    one block.
  </Accordion>

  <Accordion title="The ciphertext is a different length every time">
    That is expected. PKCS#1 v1.5 padding includes random bytes, so encrypting the same payload
    twice produces different ciphertext. Both decrypt correctly.
  </Accordion>

  <Accordion title="Should I encrypt on the client or the server?">
    The server. Even though the key is public, doing it server-side keeps card data off your
    own frontend and out of your logs, and avoids shipping an RSA implementation to the browser.
  </Accordion>
</AccordionGroup>
