Skip to main content

The signed envelope

Every signed Cheggout API — the Rewards Center token endpoints, and the Bank & SDK server APIs — uses one envelope. Implement it once and it works everywhere.

Structure

{
"CHANNELID": "YOURCHANNEL",
"SIGN": "<Base64 RSA signature>",
"BDATA": "<Base64 of the JSON payload>"
}
FieldTypeRequiredDescription
CHANNELIDstringYesYour predefined channel identifier, issued by Cheggout. Tells Cheggout which public key to verify with.
SIGNstringYesBase64 signature over the BDATA string, using SHA256withRSA and your private key.
BDATAstringYesBase64 of the UTF-8 JSON payload for this endpoint.

The business payload for the endpoint lives inside BDATA. Each API reference page documents its own payload fields.

What exactly gets signed

This is the detail that most commonly trips up a first integration:

You sign the Base64 BDATA string itself — not the decoded JSON, and not the whole envelope.

payload JSON ──Base64──► BDATA string ──sign these UTF-8 bytes──► SIGN

CHANNELID is not covered by the signature.

ParameterValue
AlgorithmSHA256withRSA (RSASSA-PKCS#1 v1.5 with SHA-256)
Signed inputUTF-8 bytes of the Base64 BDATA string
Signature encodingBase64
KeyYour RSA private key (2048-bit)
A note on wording in older documents

Some circulated documents describe SIGN as "generated using the public key of the other party." That is imprecise: you sign with your own private key, and Cheggout verifies with the public key you gave them. The reverse applies to responses.

Building a request

import java.nio.charset.StandardCharsets;
import java.security.PrivateKey;
import java.security.Signature;
import java.util.Base64;

public String buildEnvelope(String payloadJson, PrivateKey privateKey, String channelId)
throws Exception {

// 1. Base64-encode the payload
String bdata = Base64.getEncoder()
.encodeToString(payloadJson.getBytes(StandardCharsets.UTF_8));

// 2. Sign the Base64 STRING, not the original JSON
Signature signer = Signature.getInstance("SHA256withRSA");
signer.initSign(privateKey);
signer.update(bdata.getBytes(StandardCharsets.UTF_8));
String sign = Base64.getEncoder().encodeToString(signer.sign());

// 3. Assemble
Map<String, String> envelope = new LinkedHashMap<>();
envelope.put("CHANNELID", channelId);
envelope.put("SIGN", sign);
envelope.put("BDATA", bdata);
return new ObjectMapper().writeValueAsString(envelope);
}

Verifying a response

Signed endpoints reply with the same envelope shape, signed by Cheggout:

{
"CHANNELID": "CHEGGOUT",
"SIGN": "<Cheggout's signature>",
"BDATA": "<Base64 payload>"
}

Verify before you trust it.

public String verifyAndDecode(String bdata, String sign, PublicKey cheggoutPublicKey)
throws Exception {

Signature verifier = Signature.getInstance("SHA256withRSA");
verifier.initVerify(cheggoutPublicKey);
verifier.update(bdata.getBytes(StandardCharsets.UTF_8));

if (!verifier.verify(Base64.getDecoder().decode(sign))) {
throw new SecurityException("Cheggout response signature invalid");
}
return new String(Base64.getDecoder().decode(bdata), StandardCharsets.UTF_8);
}
Handle unsigned failure bodies

A failed request may come back as plain JSON with no SIGN at all — for example {"STATUS":"FAILURE","ERRORSTRING":"Signature Mismatch!"}. Check for the failure shape before attempting verification, or your client will throw on a legitimate error response.

Worked example

Payload for getSecuredReferenceToken:

{ "bName": "YOURCHANNEL" }

Base64-encoded:

eyAiYk5hbWUiOiAiWU9VUkNIQU5ORUwiIH0=

Signed and assembled:

{
"CHANNELID": "YOURCHANNEL",
"SIGN": "REPLACE_WITH_YOUR_BASE64_SIGNATURE",
"BDATA": "eyAiYk5hbWUiOiAiWU9VUkNIQU5ORUwiIH0="
}

Response, after verifying and decoding BDATA:

{
"referenceKey": "REPLACE_WITH_TOKEN",
"expireDate": "<expiry timestamp>"
}

All signatures and tokens in this documentation are placeholders. Real values come from your own key pair and Cheggout's UAT environment.

Treat tokens as opaque credentials. No token format is documented — do not parse one, and do not derive an expiry from its contents.

Common failures

SymptomLikely cause
Signature Mismatch!Signed the decoded JSON instead of the Base64 string
Signature Mismatch!Re-serialised the payload after computing BDATA — sign exactly the bytes you send
Signature Mismatch!Wrong CHANNELID, so Cheggout is verifying with a different key
Signature Mismatch!Used PSS padding instead of PKCS#1 v1.5
Payload field ignoredWrong key case — several payloads match keys case-sensitively
Connection refused / rejectedCalling IP not whitelisted

The rule for the top three: compute BDATA once, sign that exact string, and transmit that exact string.

Payload key reference

Payload keys differ per endpoint and are matched case-sensitively. Always confirm on the endpoint page.

EndpointPayload key for your channel
getSecuredReferenceTokenbName
GetAuthTokenchannelName
GenerateSessionInfoV2bName
FetchRewardInfochannelNamenote: this endpoint sends CHANNELID and BDATA but no SIGN. It is authenticated by a bearer token, not by a signature. See FetchRewardInfo.

Replay protection

TBD The envelope carries no nonce or timestamp, and the signature does not cover CHANNELID. A captured request body is therefore replayable at the transport layer; today the mitigations are IP whitelisting, TLS, and per-request idempotency via extTransactionId.

Do not design flows that assume the envelope alone prevents replay. A nonce/timestamp extension is tracked in Open Items.

Next