Technical instructions for external partners and merchants to encrypt sensitive credentials (such as OAuth client secrets, API tokens, and webhook keys) using Google’s public HPKE key for secure transmission to Google Universal Commerce Protocol (UCP).
When external partners communicate sensitive authorization material to Google UCP—such as during third-party merchant onboarding, credential rotation, or internal gateway provisioning—Google supports application-layer encryption to ensure secrets remain protected end-to-end across trust boundaries.
Data encryption uses Hybrid Public Key Encryption (HPKE), standardized in RFC 9180. Under this model:
/.well-known/ucp.json).
Public encryption keys are published in the keys array of the UCP discovery manifest and versioned profiles. Partners should look for keys with "use": "enc" and "kty": "OKP".
| Parameter | Specification | Value / Identifier |
|---|---|---|
Key Type (kty) |
RFC 8037 Octet Key Pair | OKP |
Intended Use (use) |
Public Key Encryption | enc |
Elliptic Curve (crv) |
Curve25519 (X25519) | X25519 |
| KEM (Key Encapsulation) | DHKEM(X25519, HKDF-SHA256) | 0x0020 (KEM ID 32) |
| KDF (Key Derivation) | HKDF-SHA256 | 0x0001 (KDF ID 1) |
| AEAD (Authenticated Encryption) | AES-256-GCM | 0x0002 (AEAD ID 2) |
Key ID (kid) |
Keystore Key Identifier | Dynamic hash (e.g. 7Us51w) |
Public Coordinate (x) |
Base64URL-encoded (unpadded) raw 32-byte public key | Published in discovery JSON |
{
"kty": "OKP",
"use": "enc",
"kid": "7Us51w",
"crv": "X25519",
"x": "3p7bfXt9wbTTW2HC7OQ1Nz-DQ8hbeURbgjeVCz--zVc"
}
https://ucp.goog/.well-known/ucp.jsonhttps://ucp.goog/profiles/{VERSION}/ucp.json (e.g., 2026-08-25, 2026-04-08)https://ucp.goog/.well-known/ucp.json. Parse the keys array and select the item where use == "enc" (and optionally match the active kid).
x): Decode the Base64URL-encoded string in property x to obtain the 32 raw public key bytes.
KEM: DHKEM(X25519, HKDF-SHA256), KDF: HKDF-SHA256, AEAD: AES-256-GCM."") for info / application context and aad (associated data).externally_encrypted_client_secret field of your API request payload.
Using the official Google Tink library (pip install tink) or standard cryptography libraries:
import base64
import json
import urllib.request
import tink
from tink import hybrid
# 1. Fetch UCP Discovery Profile to get active encryption key
req = urllib.request.urlopen("https://ucp.goog/.well-known/ucp.json")
profile = json.loads(req.read().decode("utf-8"))
# 2. Extract the encryption key (use == "enc" and kty == "OKP")
enc_key = next(k for k in profile.get("keys", []) if k.get("use") == "enc" and k.get("kty") == "OKP")
x_coord_bytes = base64.urlsafe_b64decode(enc_key["x"] + "==")
# 3. Encrypt secret with Tink HPKE (DHKEM_X25519_HKDF_SHA256, HKDF_SHA256, AES_256_GCM)
hybrid.register()
# Example with standard HPKE library:
from hpke import CipherSuite, KEMId, KDFId, AEADId
suite_x25519 = CipherSuite(KEMId.DHKEM_X25519_HKDF_SHA256, KDFId.HKDF_SHA256, AEADId.AES256_GCM)
plaintext_secret = "my_partner_oauth_client_secret_xyz"
encapsulated_key, ciphertext = suite_x25519.setup_sender_and_seal(
public_key=x_coord_bytes,
info=b"",
aad=b"",
plaintext=plaintext_secret.encode("utf-8")
)
raw_ciphertext = encapsulated_key + ciphertext
# 4. Base64 encode for transmission
encoded_secret = base64.b64encode(raw_ciphertext).decode("utf-8")
print(f"Externally Encrypted Secret: {encoded_secret}")
Using Google Tink Go (github.com/tink-crypto/tink-go/v2):
package main
import (
"context"
"encoding/base64"
"fmt"
"github.com/tink-crypto/tink-go/v2/hybrid"
"github.com/tink-crypto/tink-go/v2/keyset"
)
// EncryptSecret encrypts a raw client secret with a Tink HPKE public keyset handle.
func EncryptSecret(ctx context.Context, pubHandle *keyset.Handle, secret string) (string, error) {
encrypter, err := hybrid.NewHybridEncrypt(pubHandle)
if err != nil {
return "", fmt.Errorf("failed to create hybrid encrypter: %w", err)
}
ciphertext, err := encrypter.Encrypt([]byte(secret), nil)
if err != nil {
return "", fmt.Errorf("failed to encrypt secret: %w", err)
}
return base64.StdEncoding.EncodeToString(ciphertext), nil
}
Using @hpke/core:
import { Aes256Gcm, CipherSuite, DhkemX25519HkdfSha256, HkdfSha256 } from "@hpke/core";
async function encryptUcpSecret(secretText: string, rawPublicKeyBase64Url: string): Promise<string> {
// Decode base64url X coordinate to raw 32-byte public key
const rawKey = Buffer.from(rawPublicKeyBase64Url, "base64url");
const suite = new CipherSuite({
kem: new DhkemX25519HkdfSha256(),
kdf: new HkdfSha256(),
aead: new Aes256Gcm(),
});
const publicKey = await suite.kem.importKey("raw", rawKey, true);
const sender = await suite.createSenderContext({
recipientPublicKey: publicKey,
});
const ciphertext = await sender.seal(new TextEncoder().encode(secretText));
// Combine encapsulated key + ciphertext
const combined = new Uint8Array(sender.enc.byteLength + ciphertext.byteLength);
combined.set(new Uint8Array(sender.enc), 0);
combined.set(new Uint8Array(ciphertext), sender.enc.byteLength);
return Buffer.from(combined).toString("base64");
}
Using Google Tink Java:
import com.google.crypto.tink.HybridEncrypt;
import com.google.crypto.tink.KeysetHandle;
import com.google.crypto.tink.hybrid.HybridConfig;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
public class UcpEncryptor {
static {
try {
HybridConfig.register();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public static String encryptSecret(KeysetHandle publicKeysetHandle, String secret) throws Exception {
HybridEncrypt encryptor = publicKeysetHandle.getPrimitive(HybridEncrypt.class);
byte[] ciphertext = encryptor.encrypt(secret.getBytes(StandardCharsets.UTF_8), null);
return Base64.getEncoder().encodeToString(ciphertext);
}
}
When making requests to Google UMIS (e.g. EncryptClientSecret), supply the encrypted secret using the externally_encrypted_client_secret property:
POST /rpc/commerce.delivery.merchantintegrations.umis.internal.DebugService/EncryptClientSecret
Content-Type: application/json
{
"partner_id": "PARTNER_UNIQUE_ID",
"externally_encrypted_client_secret": "CpYBCokBCjV0eXBlLmdvb2dsZWFwaXMuY29t..."
}
HTTP/1.1 200 OK
Content-Type: application/json
{
"encrypted_client_secret": "encrypted_secret=...=PARTNER_UNIQUE_ID"
}
| Status Code | Condition | Resolution |
|---|---|---|
400 INVALID_ARGUMENT |
Partner ID is missing, or neither client_secret nor externally_encrypted_client_secret was provided. |
Provide a valid partner_id and ensure the payload contains externally_encrypted_client_secret. |
500 INTERNAL |
Ciphertext decryption failed. | Verify that: (1) the ciphertext is valid Base64, (2) the active public key from ucp.json was used, (3) HPKE parameters match DHKEM(X25519) / SHA-256 / AES-256-GCM. |
Google periodically rotates encryption keys in accordance with cryptographic security best practices: