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

# Authentication Guide

> Everything in one place: apiKey/secret/signing (P0 revision) + secure retrieval of production credentials + RSA key generation + the three IP whitelists

## 📄 Guide

Whether you are an exchange, a wallet or a platform, once DCS hands you a key pair you can call every `/open-api/` endpoint with one uniform signing rule. This page gets your authentication working in a single pass. As a licensed issuer with its own BINs, DCS requires a verifiable origin and replay protection on every API call.

Three topics are covered here: **HMAC signing** for API calls, **secure retrieval** of production credentials, and the **RSA public key** used for authorization notifications.

***

## 1. Credentials and Request Headers

### What DCS gives you

| Field       | Description                                                                                                | Provided by |
| :---------- | :--------------------------------------------------------------------------------------------------------- | :---------- |
| `apiKey`    | Globally unique identifier for calling `/open-api/`; used for identification and request-origin tracing    | DCS         |
| `secretKey` | The key used to compute the request signature. **Keep it secure and never disclose it to any third party** | DCS         |

> For sandbox `apiKey`/`secretKey`, contact the DCS team. Production credentials must go through the [secure retrieval process](#3-secure-retrieval-of-production-credentials).

### Headers required on every request

Every partner call to `/open-api/` must carry the following HTTP headers. A JSON request body additionally requires `Content-Type: application/json`, which the public API reference omits but whose absence causes a `415` error:

| Header             | Required | Description                                                                                                                                                                                                                                                                                                                                       |
| :----------------- | :------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `Content-Type`     | REQUIRED | Fixed value `application/json`. ❗Neither the published header table nor the curl sample states it, but any JSON request body must carry it, otherwise the backend falls back to the Spring default and returns `415 Unsupported Media Type`. This is the single most common "copied the docs, still fails" issue in the first mile of integration |
| `X-DAPI-API-KEY`   | REQUIRED | The `apiKey` issued by DCS                                                                                                                                                                                                                                                                                                                        |
| `X-DAPI-TIMESTAMP` | REQUIRED | Request timestamp (Unix epoch milliseconds, timezone-independent), used for replay protection                                                                                                                                                                                                                                                     |
| `X-DAPI-NONCE`     | REQUIRED | Random number in the range `[10000, 99999]`; generate a fresh one for every request                                                                                                                                                                                                                                                               |
| `X-DAPI-SIGN`      | REQUIRED | HMAC-SHA256 signature computed per the rules below (lowercase hexadecimal)                                                                                                                                                                                                                                                                        |

<Note>
  `secretKey` is used only to compute the signature locally. Never transmit it in cleartext in a request header.
</Note>

***

## 2. Request Signing (HMAC-SHA256)

### Signing rule

Use the **HmacSHA256** algorithm with `secretKey` as the key, and sign the concatenated string:

```
X-DAPI-SIGN = HmacSHA256( apiKey + timestamp + nonce + payload , secretKey )
```

The output is a lowercase hexadecimal string.

**What each segment of the concatenated string is:**

| Segment     | Value                                                                                       |
| :---------- | :------------------------------------------------------------------------------------------ |
| `apiKey`    | Same as `X-DAPI-API-KEY`                                                                    |
| `timestamp` | Same as `X-DAPI-TIMESTAMP` (millisecond timestamp)                                          |
| `nonce`     | Same as `X-DAPI-NONCE`                                                                      |
| `payload`   | **GET** requests: the URL-encoded query string; **all other methods**: the raw request body |

```text theme={null}
if method is GET:
    payload = url.encodedQuery()     // e.g. cardOrderRef=14
else:
    payload = body                   // raw JSON request body string
```

> The signing algorithm is fixed to HmacSHA256. Always use it.

### Replay protection (who does what: DCS verifies / partner generates)

* **TIMESTAMP**: DCS accepts requests within a **5-second** window by default; anything later is rejected outright. Keep your local clock synchronized to UTC.
* **NONCE**: Generate a random number in `[10000, 99999]` for every request and never reuse one. Partners should still use idempotency keys for business write operations and avoid replaying an identical request inside the 5-second window.

### Signing implementation examples

**JavaScript (Postman pre-request script)**

```javascript theme={null}
const CryptoJS = require('crypto-js');

const apiSecret = '{your secret key}';
const apiKey    = '{your api key}';

// GET uses the query string; other methods use the request body. Concatenate whichever applies.
const queryParams  = '{your query param}';   // e.g. cardOrderRef=14
const requestBody  = '{your request body}';  // raw body for POST/PUT
const nonce        = 10010;
const timestamp    = Date.now().toString();

const dataToSign = apiKey + timestamp + nonce + queryParams + requestBody;
const signature  = CryptoJS.HmacSHA256(dataToSign, apiSecret).toString(CryptoJS.enc.Hex);
```

**Java**

```java theme={null}
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import org.apache.commons.codec.binary.Hex;

public final class HmacSignature {
    private static final String HMAC_SHA256 = "HmacSHA256";

    public static String getSignature(String apiSecret, String data) throws Exception {
        SecretKeySpec keySpec = new SecretKeySpec(apiSecret.getBytes(), HMAC_SHA256);
        Mac mac = Mac.getInstance(HMAC_SHA256);
        mac.init(keySpec);
        byte[] hmac = mac.doFinal(data.getBytes());
        return Hex.encodeHexString(hmac);
    }
}
```

### Full request example (GET)

```bash theme={null}
curl --location 'https://{domain}/open-api/card-order/v1/detail?cardOrderRef=14' \
  --header 'Content-Type: application/json' \
  --header 'X-DAPI-API-KEY: 697EA72DACF742F280943DAB211E6C2B' \
  --header 'X-DAPI-TIMESTAMP: 1743044911331' \
  --header 'X-DAPI-NONCE: 10100' \
  --header 'X-DAPI-SIGN: b979d2ea5c70187ddac7d7a5937c40c87a58f765594b740e596d25fca96f2dcb'
```

> Here `payload = cardOrderRef=14` (the GET query string). To validate your implementation, first reproduce the same `X-DAPI-SIGN` from a set of `apiKey/timestamp/nonce/payload` values supplied by DCS, then go live.

### Uniform response envelope

Every `/open-api/` endpoint returns the same envelope:

```json theme={null}
{
  "code": "SYS_SUCCESS",
  "message": null,
  "messageDetail": null,
  "data": { }
}
```

| Field           | Description                                                                                                |
| :-------------- | :--------------------------------------------------------------------------------------------------------- |
| `code`          | Business status code. `SYS_SUCCESS` on success; a specific error code on failure (see the error code page) |
| `message`       | Short message, usually `null` on success                                                                   |
| `messageDetail` | Detail object, usually `null`                                                                              |
| `data`          | Business payload                                                                                           |

> Judge success by `code == "SYS_SUCCESS"`. On failure, `code` carries the specific error code (see the error code page).

***

## 3. Secure Retrieval of Production Credentials

To prevent production `apiKey`/`secretKey` values from leaking in transit, **production credentials are never sent to you directly**. They are collected through a one-time secure retrieval flow.

> The sandbox does not use this flow; just contact the DCS team for sandbox credentials.

### The flow (and who does what)

1. **Partner provides**: one secure email address plus the IP that will perform the retrieval.
   * The email address receives the retrieval instructions; that IP is added to the credential-retrieval whitelist.
2. **DCS sends**: an email arrives at the secure address containing a temporary secure link that is **valid exactly once**.
3. **Partner retrieves**: concatenate `extractUrl` with `extractSecretKey` and **run it from the machine at the designated IP** to receive the `apiKey`/`secretKey`.

### Fields in the email

| Field              | Type   | Description                                               |
| :----------------- | :----- | :-------------------------------------------------------- |
| `expireTime`       | string | Validity period of the retrieval security code            |
| `extractSecretKey` | string | Credential retrieval security code                        |
| `extractUrl`       | string | Credential retrieval URL                                  |
| `howToUse`         | string | Usage instructions                                        |
| `notes`            | string | Reminder that the link can be used for one retrieval only |

**Success response**

```json theme={null}
{
  "code": "SYS_SUCCESS",
  "message": null,
  "messageDetail": null,
  "data": {
    "expireTime": "2024-10-21T16:52+08:00[Asia/Shanghai]",
    "extractSecretKey": "4f72b1e9c49e4ac1bbc2cd12b5e44993",
    "extractUrl": "https://{domain}/internal/open-api/v1/secret-extract/",
    "howToUse": "Please concatenate the url with the secret-key and execute it on the specified machine.",
    "notes": "This link is only valid for one AKSK extraction, if the content is not properly accessed, the AKSK may have been compromised, please contact us promptly."
  }
}
```

**Failure response**

```json theme={null}
{
  "code": "ERROR-CODE",
  "message": "simple describe, see error-code list",
  "messageDetail": null,
  "data": null
}
```

<Warning>
  **If the retrieval fails**: the link may already have been consumed while still within its validity period, which means the credentials may be compromised. Contact your DCS business contact immediately and repeat the email flow to have them reissued.
</Warning>

***

## 4. RSA Public Key (authorization notifications only)

Authorization forwarding notifications (DCS to the partner `auth_url`) protect sensitive data such as transaction amount and currency with **RSA-2048 mutual signing plus encryption**. This is a mechanism entirely separate from the HMAC used for API calls. Authorization notifications add no AES/IV layer: the business fields are encrypted as a whole using segmented RSA, and the signature algorithm is `SHA256withRSA`.

* **HMAC**: used when a partner calls `/open-api/` endpoints (sections 1 and 2 of this page).
* **RSA**: used when DCS pushes authorization notifications to a partner, protecting sensitive fields along the authorization path.

The partner generates its own RSA key pair, hands the **public key** (`external_public_key`) to DCS, and keeps the private key.

### How to generate (who does it: partner)

```bash theme={null}
# 1. Generate a 2048-bit RSA private key
openssl genpkey -algorithm RSA -out private_key.pem -pkeyopt rsa_keygen_bits:2048

# 2. Derive the public key from the private key
openssl rsa -pubout -in private_key.pem -out public_key.pem

# 3. Strip the header, footer and line breaks to get the single-line public key string to submit to DCS
cat public_key.pem | sed '/BEGIN/d; /END/d' | tr -d '\n'
```

The single-line string from step 3 is your `external_public_key`. Provide it to DCS together with `auth_url`.

### DCS RSA public key (provided by DCS)

Partners use the DCS RSA public key to verify DCS notifications and to encrypt their responses.

* **Sandbox**:

```text theme={null}
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA7FDHTQgX8vDvBZMlOOQm
xqoEw/gvk2bRQFa8UooAq4fWkChBpWM+bi0vu3T00rxqenbFDZoz5QE+/Mhhk+4X
Nvo47rSnhP6Nhkjivo8S8wUuHZbk20EwaqVVVpjYVvdFWSeLHkumofdj5HdbgzUI
nqslVdtAGMYJCzH0KlfWnVUuW2BnIk0wro68rNk0bqqbCkcnLLXpyElKxZCAvvo4
LRYcS81jiG78a6EzgbzLjuNvYH5vOj9dh49QevTBCyBUC35JVt85VC3RUGny9CKL
dn3x9XOLKRdndYEWGU5ZJVZdioNgWhU40TBVjveqbORSs+ApAZOWTovrKvABthXp
hQIDAQAB
-----END PUBLIC KEY-----
```

* **Production**: contact the DCS team.

### Java example for encryption, decryption and signing

The signature algorithm is `SHA256withRSA` with a 2048-bit key, and encryption and decryption run in blocks (245-byte encryption block / 256-byte decryption block):

```java theme={null}
import javax.crypto.Cipher;
import java.io.ByteArrayOutputStream;
import java.security.*;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;

public class RSAUtil {
    private static final String ALGORITHM = "RSA";
    private static final int KEY_SIZE = 2048;
    private static final int MAX_ENCRYPT_BLOCK = KEY_SIZE / 8 - 11; // 245
    private static final int MAX_DECRYPT_BLOCK = KEY_SIZE / 8;      // 256

    // Encrypt with the public key (segmented)
    public static String encrypt(String data, PublicKey publicKey) throws Exception {
        Cipher cipher = Cipher.getInstance(ALGORITHM);
        cipher.init(Cipher.ENCRYPT_MODE, publicKey);
        byte[] encryptedBytes = doSegment(cipher, data.getBytes(), MAX_ENCRYPT_BLOCK);
        return Base64.getEncoder().encodeToString(encryptedBytes);
    }

    // Decrypt with the private key (segmented)
    public static String decrypt(String encryptedData, PrivateKey privateKey) throws Exception {
        byte[] dataBytes = Base64.getDecoder().decode(encryptedData);
        Cipher cipher = Cipher.getInstance(ALGORITHM);
        cipher.init(Cipher.DECRYPT_MODE, privateKey);
        byte[] decryptedBytes = doSegment(cipher, dataBytes, MAX_DECRYPT_BLOCK);
        return new String(decryptedBytes);
    }

    // Rebuild a public key from a Base64 string
    public static PublicKey parsePublicKey(String base64PublicKey) throws Exception {
        byte[] keyBytes = Base64.getDecoder().decode(base64PublicKey);
        return KeyFactory.getInstance(ALGORITHM).generatePublic(new X509EncodedKeySpec(keyBytes));
    }

    // Rebuild a private key from a Base64 string
    public static PrivateKey parsePrivateKey(String base64PrivateKey) throws Exception {
        byte[] keyBytes = Base64.getDecoder().decode(base64PrivateKey);
        return KeyFactory.getInstance(ALGORITHM).generatePrivate(new PKCS8EncodedKeySpec(keyBytes));
    }

    // Sign with the private key (SHA256withRSA)
    public static String sign(String data, PrivateKey privateKey) throws Exception {
        Signature signature = Signature.getInstance("SHA256withRSA");
        signature.initSign(privateKey);
        signature.update(data.getBytes());
        return Base64.getEncoder().encodeToString(signature.sign());
    }

    // Verify with the public key
    public static boolean verify(String data, String sign, PublicKey publicKey) throws Exception {
        Signature signature = Signature.getInstance("SHA256withRSA");
        signature.initVerify(publicKey);
        signature.update(data.getBytes());
        return signature.verify(Base64.getDecoder().decode(sign));
    }

    private static byte[] doSegment(Cipher cipher, byte[] data, int maxBlock) throws Exception {
        int inputLen = data.length;
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        int offset = 0;
        while (offset < inputLen) {
            int blockLen = Math.min(inputLen - offset, maxBlock);
            out.write(cipher.doFinal(data, offset, blockLen));
            offset += blockLen;
        }
        return out.toByteArray();
    }
}
```

> For the full encryption, decryption and signature verification details of authorization notifications, see [Authorization Forwarding and Encryption](../how-to-use/transactions/authorization).

***

## 5. IP Whitelists (three types, all required)

DCS whitelists every direction of traffic. Partners must give DCS their static egress IPs **separately for sandbox and production**, covering these three directions:

| Whitelist                          | Direction                        | Purpose                                                                            | Provided by |
| :--------------------------------- | :------------------------------- | :--------------------------------------------------------------------------------- | :---------- |
| API call whitelist                 | Partner to DCS                   | Egress address used to call `/open-api/` endpoints                                 | Partner     |
| Webhook whitelist                  | DCS to the partner `webhook_url` | Egress address that receives card order, transaction and other event notifications | Partner     |
| Authorization forwarding whitelist | DCS to the partner `auth_url`    | Egress address that receives authorization forwarding requests                     | Partner     |

> Configure sandbox and production separately and never mix them. The credential-retrieval IP (section 3) and the API call IP may differ; tell DCS which purpose each one serves.

***

## Next steps

With authentication working, head to [Quickstart](../getting-started/quickstart) to issue your first card, or go back to [First Steps](../getting-started/first-steps) to confirm that provisioning, callbacks and whitelists are in place. For the RSA encryption and decryption details of authorization notifications, see [Authorization Forwarding and Encryption](../how-to-use/transactions/authorization).
