Vouchers
Request encryption guide
Encrypt voucher paths, tokens, request bodies, and responses inside HTTPS with P-256 ECDH, HKDF-SHA256, and AES-256-GCM.
Each session negotiates a shared secret with an ephemeral client key and the current server public key.
Derives separate keys by brand, origin, session, channel, and direction.
Authenticates and encrypts requests and responses independently; tampering fails decryption.
When to enable it
Use required mode for production server calls. Even when TLS terminates at a gateway or proxy, business data is plaintext only inside the secure client and IM gateway. The integration demo API console can compare encrypted and regular HTTPS requests.
Download server integration examples
The examples match the current gateway protocol and belong only in the merchant backend. They are not App, Android, or browser client code. Never commit the sop_ token.
mkdir -p secure-transport
curl -fsSLO "$SITE/examples/secure-transport/index.js" \
--output-dir secure-transport
curl -fsSLO "$SITE/examples/secure-transport/client.js" \
--output-dir secure-transportUse Node.js 20 or an equivalent runtime with Fetch, Web Crypto, btoa, and atob. client.js imports index.js from the same directory. When the application policy is not *, set IM_REQUEST_ORIGIN to an allowed full origin; the client binds it to the secure session and sends the Origin header.
The Java ZIP is a buildable merchant-backend Maven project with the secure transport library, connection check, issuance example, and setup guide. It requires JDK 17+.
Complete issuance example
import { SecureTransportClient } from './secure-transport/client.js'
const API_ORIGIN = 'https://api.example.com'
const client = new SecureTransportClient({
baseUrl: `${API_ORIGIN}/api/v1`,
mode: 'required',
pageOrigin: process.env.IM_REQUEST_ORIGIN || '',
timeoutMs: 15_000,
})
const externalIssueId = 'points_order_20260909_001'
const idempotencyKey = `issue:${externalIssueId}`
const result = await client.dispatch({
method: 'POST',
path: '/api/v1/open-platform/vouchers/issue',
query: {},
headers: {
accept: 'application/json',
authorization: `Bearer ${process.env.IM_OPEN_PLATFORM_TOKEN}`,
'content-type': 'application/json',
'idempotency-key': idempotencyKey,
},
body: {
template_id: process.env.IM_POINTS_TEMPLATE_ID,
external_issue_id: externalIssueId,
recipient_user_id: 'AUTHORIZED_SOCHAT_USER_ID',
},
})
if (!result.secure) throw new Error('安全传输未建立')
if (result.status < 200 || result.status >= 300 || result.body?.success === false) {
throw new Error(result.body?.message || `IM API ${result.status}`)
}
console.log(result.body.data)Protocol flow
- 1. Bootstrap GET /api/v1/secure/bootstrap, validate v, suite, enabled, kid, and serverPublicKey, then synchronize with serverTime.
- 2. Session Generate an ephemeral P-256 key pair and POST the public key to /api/v1/secure/session; retain sid, salt, brand, origin, and expiresAt.
- 3. Derive Run ECDH, then use HKDF to derive separate 256-bit http/c2s and http/s2c keys.
- 4. Encrypt Generate a fresh rid and 96-bit IV for every request, then encrypt method, path, query, headers, and body together.
- 5. Dispatch POST /api/v1/secure/dispatch. The server validates time, session, origin, and rid against replay before executing the decrypted logical endpoint.
- 6. Decrypt Authenticate and decrypt the response with the same rid and s2c AAD, then handle the inner business status.
Request visible on the wire
POST /api/v1/secure/dispatch
Content-Type: application/vnd.sochat.secure+json
{
"v": 1,
"suite": "P256-HKDF-SHA256-A256GCM",
"kid": "2026-09",
"sid": "st_...",
"rid": "rid_...",
"ts": 1788912000000,
"iv": "base64url-96-bit-iv",
"ciphertext": "base64url-ciphertext-and-128-bit-tag"
}Authorization, logical path, idempotency key, and business JSON are inside ciphertext. The outer request only exposes protocol fields and permitted tracing headers.
Cryptographic parameters
| Item | Rule |
|---|---|
suite | P256-HKDF-SHA256-A256GCM |
HKDF info | soim|v1|{brand}|{origin or -}|{sid}|http|{c2s or s2c} |
AAD | soim|v1|http|{c2s|s2c}|{kid}|{sid}|{rid}|{ts} |
IV / tag | Fresh random 96-bit IV per message; the 128-bit GCM tag is encoded with ciphertext. |
encoding | All binary fields use unpadded base64url. |
clock / session | The default clock skew is 60 seconds and session lifetime is 600 seconds; the client renews five seconds before expiry. |
Sessions, retries, and idempotency
- On SECURE_SESSION_EXPIRED or SECURE_SESSION_MISMATCH, clear the session, renegotiate, and retry only once.
- A retry gets a new rid and IV, while the business Idempotency-Key, external_issue_id, and body must remain identical.
- Each rid is single-use within a session; replaying the same ciphertext returns REQUEST_REPLAYED.
- Concurrent requests retain independent session-key copies and zero them afterward; never log or persist session keys.
Common secure transport errors
Secure transport errors happen before the business endpoint. Business 4xx responses are encrypted normally and appear in decrypted result.status and result.body.
SECURE_TRANSPORT_REQUIRED · 426The gateway requires encrypted transport, but the request used the regular business URL.REQUEST_TIMESTAMP_OUT_OF_RANGEClient clock drift is too large; refresh bootstrap.serverTime.SECURE_SESSION_EXPIREDThe session expired or was removed; renegotiate and retry once.SECURE_SESSION_ORIGIN_MISMATCHSession creation and dispatch origins differ. Node server integrations normally use an empty origin consistently.DECRYPTION_FAILEDKey, AAD, IV, ciphertext, or authentication tag does not match.REQUEST_REPLAYEDThe sid and rid pair was already processed; generate a new rid.Production checks
- Keep the sop_ token only in the inner authorization header and trusted server environment variables.
- The logical path must start with /api/v1/ and cannot contain a query, hash, backslash, or /secure/; put query values in the query object.
- Secure dispatch supports JSON only. Binary or streaming responses and envelopes above the server limit must use their direct endpoints.
- Log inner status, business requestId, and secure error codes; never log tokens, voucher codes, plaintext keys, salt, or full ciphertext.
