Ripple Custody protects critical entities, such as domains, users, accounts, and intents, with a data integrity protocol. The API returns each protected entity in a signed envelope with three fields: data, signature, and signingKey. The notary signs the envelope with its API-signing key.
The SDKs verify supported signed envelopes before they return data from successful responses. Signature verification detects changes to signed data, even when TLS is in place. The SDKs don't verify every object, even when you enable response verification. See Verification limits for the exceptions. Use this page to pin the notary key, handle verification failures, and verify stored payloads.
Response verification is on by default in all three SDKs. For each successful (2xx) JSON response, the SDK checks supported envelopes:
- Finds the signed envelopes in the body, either a single envelope or the items of a collection.
- Checks that each envelope's
signingKeymatches the notary API public key you pinned in the configuration. - Verifies the
signatureover the canonical form ofdata.
If a signature or pinned-key check fails, the call throws ResponseSignatureError and doesn't return the data. Responses that the platform doesn't sign, such as balances or the requestId returned when you submit an intent, pass through unchanged.
The SDK never fetches the notary key on its own. Reading the key from the same API whose responses it verifies would let a compromised server supply its own key. You must pin the key from a source you trust.
- TypeScript: the SDK returns objects that contain
dataandsignaturebut nosigningKeywithout verifying their signatures. It logs a warning instead of throwing an error. Some platform responses, such as tickers, use this format. Don't treat these objects as verified data. The manual verifier returns0if it doesn't verify any envelopes. - Python: the SDK skips signature verification for
CoreAccountAddressenvelopes when bothsignatureandsigningKeyare null. It also skips these envelopes inCoreAddressesCollectionand emits aCustodyWarning. Newly generated external account addresses can use this format. The SDK doesn't verify the signature on an address it skips. - All SDKs: the SDKs don't verify unsigned responses or objects whose format the verifier doesn't recognize. The manual verifier counts the envelopes it verifies. The count doesn't confirm that the verifier checked the entire payload.
If an operation requires verified data, check how many envelopes the verifier checked. Stop if it skips an object you need. For a single signed envelope, require a count of 1. For collections, compare the count with the number of items you expect to carry signatures. Investigate verification warnings. Don't suppress them or disable verification.
If verification is on and you haven't configured a notary API public key, the SDK refuses to build a client instead of running without verification:
| SDK | Where it fails | Error |
|---|---|---|
| TypeScript | loadConfiguration() | CustodyError |
| Java | CustodyClient.fromConfig(config) or new CustodyClient(config) | CustodyError |
| Python | load_configuration() | ConfigurationError |
To fix the error, pin the key as described in Configure the key. To run without verification, see Disable verification.
You need the notary's API-signing public key, encoded as a base64 DER (Distinguished Encoding Rules) SubjectPublicKeyInfo value. The notary has other keys, such as the messaging key and the collections key. They don't verify API responses, and pinning one of them causes every protected response to fail.
Get the key out of band from a source you trust:
- If your organization operates Ripple Custody, ask the team that deployed the notary. The notary generates its API key during setup, and again during each key rotation. See Notary key rotation and migration.
- If Ripple hosts your deployment, contact the Ripple support team.
To cross-check the value, call List API public keys and compare items[].data.publicKey with the key you received. Treat this only as a check: don't build a client that discovers its pinned key from the API at startup.
The RC_NOTARY_API_PUBLIC_KEY environment variable works the same way in all three SDKs:
export RC_NOTARY_API_PUBLIC_KEY="MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE..."The config.toml key is notary_api_public_key in all three SDKs. The accepted key formats and the programmatic setter differ by SDK, as the following tabs show. Environment variables override the file.
notary_api_public_key = "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE..."The SDK accepts a PEM (Privacy-Enhanced Mail) or base64 DER key and normalizes it. If you build the configuration in code with generateNewConfiguration, pass the key as notaryApiPublicKey.
After a notary key rotation, responses arrive signed with the new API-signing key.
Each SDK pins one key. Update the configured key when the new notary becomes active and restart your application. Until you do, protected responses fail with ResponseSignatureError.
For the rotation procedure, see Notary key rotation and migration.
ResponseSignatureError is a subclass of CustodyError. It means one of the following:
- A key other than the one you pinned signed the response. The usual cause is a notary key rotation, or a pinned key that isn't the API-signing key.
- The signature doesn't match the data, or the envelope isn't well formed. This can mean that something altered the response between the notary and your application, for example a proxy that rewrites response bodies.
Don't retry the call or turn off verification to get past the error. Confirm the pinned key with your operator first. If the key is correct, treat the response as untrusted and investigate the network path.
The following example shows where to handle the error:
import { ResponseSignatureError } from '@ripple/ripple-custody-client-lib-typescript';
try {
await client.governance.domains.list({ limit: 10 });
} catch (err) {
if (err instanceof ResponseSignatureError) {
// Don't use the data. Check the pinned notary key, then alert.
}
throw err;
}The client checks supported response envelopes automatically, with the exceptions in Verification limits. To check a payload from another source or a response body you saved earlier, use the client's manual verification method. It uses the configured key unless you pass another key. It returns the number of envelopes it verified.
Preserve the raw JSON response. Converting it to SDK models and back to JSON can change signed fields and cause verification to fail. Parse the original JSON as shown in these examples instead.
The following examples require exactly one verified envelope in a stored response body, responseText (response_text in Python). For a collection, use the expected number of signed items instead:
const count = client.verifySigned(JSON.parse(responseText));
if (count !== 1) {
throw new Error(`Expected one verified envelope, got ${count}`);
}Turn verification off only for local development or a test cluster where no notary API key is available. Don't disable it in production: without it, your application trusts API responses on the strength of TLS alone. The TypeScript and Python SDKs treat the opt-out as deprecated and log a warning when you use it.
To opt out in any SDK, set RC_VERIFY_RESPONSES=false. Each SDK also has its own options:
verify_responses = falseIf you build the configuration in code with generateNewConfiguration, pass verifyResponses: false.
- Configure the SDK: configuration keys and environment variables for each SDK.
- Errors, pagination, and HTTP settings: the
CustodyErrorhierarchy and HTTP transport settings. - Notary key rotation and migration: the procedure that changes the API-signing key.