Building a Secure Messaging SDK for Transaction Confirmations Using RCS Without Exposing Secrets
SDKmessagingmobile

Building a Secure Messaging SDK for Transaction Confirmations Using RCS Without Exposing Secrets

nnftpay
2026-02-19
12 min read
Advertisement

Design patterns and SDK guidance to send RCS/WebPush transaction confirmations securely, keeping private keys on device and avoiding secrets leakage.

Hook: Transaction confirmations that don’t leak your keys

Sending transaction confirmations to mobile wallets using carrier channels is attractive: it meets users where they are and improves conversion for on‑chain payments. But for builders and IT teams the obvious questions are: How do I use RCS or WebPush to confirm transactions without exposing seeds, private keys, or sensitive transaction payloads? And how do I do that reliably across devices, carriers, and regulatory constraints in 2026?

Executive summary — what you’ll get from this guide

This article lays out pragmatic SDK design patterns and sample code for building a secure messaging SDK for transaction confirmations that uses RCS and WebPush while ensuring secrets never leave the wallet device. You’ll get:

  • Architectural patterns for secure, privacy‑preserving confirmation flows
  • Key management and crypto primitives you should use (and why)
  • Code patterns for server SDK and mobile SDK (Node.js + Kotlin/Swift pseudocode)
  • Fallback strategies (WebPush, in‑app, SMS safe practices)
  • Operational and compliance controls for 2026 (E2EE RCS adoption, VAPID, device attestation)

The 2026 context: RCS, WebPush and why it matters now

By 2026 the landscape for rich messaging has matured. The GSMA Universal Profile 3.0 and ongoing RCS E2EE work (MLS‑based and MLS hybrid approaches) mean many carriers and OEMs now support end‑to‑end encrypted RCS or have rolled out stronger encryption options. Meanwhile, WebPush remains the most reliable fallback for browser-based wallets, using VAPID and payload encryption standards. That combination creates an opportunity: use RCS as a first-class confirmation channel where E2EE is available, and fall back safely to WebPush or in‑app flows elsewhere.

Design goals — constraints every secure messaging SDK must meet

  1. Never expose private keys or seeds — keys remain on the wallet device (custodial or non‑custodial scenarios both supported).
  2. Messages must be confidential and authentic — only intended wallet reads the payload and verifies origin.
  3. Low friction UX — confirmations should be a one‑tap approve/reject where possible.
  4. Fallback resiliency — robust fallbacks to WebPush and secure in‑app flows when RCS E2EE isn’t available.
  5. Auditability & retention controls — logs for compliance, minimal message retention, and encryption at rest.

Why you must avoid sending raw transactions or seeds over messaging channels

Messaging channels (even E2EE ones) are not a substitute for device key custody. If an attacker obtains a message or a push token they should never be able to reconstruct a seed or craft a valid signature. The correct pattern is: send a transaction digest or console‑friendly representation and require the wallet's private key to sign locally. Never ship complete private keys, mnemonic phrases, or pre‑signed transactions through RCS/WebPush/SMS.

High‑level architecture — server + messaging gateway + wallet

Use a small deployment pattern: a server SDK (merchant backend / relayer), a messaging gateway (RCS provider + WebPush VAPID stack), and a mobile SDK inside the wallet app that holds user keys and performs local signing.


  [Merchant Backend] ---(1)---> [Messaging Gateway (RCS/WebPush)] ---(2)---> [Carrier / Push Service] ---(3)---> [Wallet App]

  1. Merchant builds transaction payload (unsigned or meta-tx) and registers request with server SDK.
  2. Server encrypts a transaction confirmation blob to the wallet's public key and sends it via messaging gateway.
  3. Wallet receives, decrypts, displays human-readable summary; user approves -> wallet signs locally and submits.
  

Core cryptography: public keys, ephemeral session keys and AEAD

The secure pattern is hybrid encryption: use asymmetric keys to derive an ephemeral symmetric key, then encrypt the payload with an AEAD cipher (AES‑GCM or ChaCha20‑Poly1305). Recommended primitives in 2026:

  • X25519 for ECDH (key agreement)
  • HKDF for key derivation
  • AES‑GCM or ChaCha20‑Poly1305 for AEAD
  • Ed25519 to sign server-generated nonces/metadata so wallets can authenticate the origin

Why this mix? X25519 + HKDF enables compact, fast shared secrets on mobile. AEAD ensures both confidentiality and integrity. Ed25519 yields efficient signatures that wallets can verify with a known server public key.

Device registration & public key distribution

Each wallet device generates a key pair at install time and registers the public key with the merchant's server (via the wallet's user account). The registration flow should include device attestation to bind that public key to the physical device:

  • Android: Play Integrity attestation / Keymaster-backed keys.
  • iOS: DeviceCheck or App Attest and secure enclave key generation.

The server stores only the device public key and associated push tokens (RCS handle, WebPush subscription info). Push tokens must be treated as sensitive: rotate them periodically, and keep them encrypted in your server KMS.

Message payload design — what to send (and what NOT to send)

Send the minimum necessary. A recommended JSON confirmation blob (encrypted) looks like this:


  {
    "version": "1.0",
    "tx_id": "uuid-1234",
    "chain": "eth:1",
    "from": "0x123...",
    "to": "0xabc...",
    "amount": "0.5 ETH",
    "gas_est": "0.002 ETH",
    "human_summary": "Purchase: NFT #345 - 0.5 ETH",
    "nonce": "base64-nonce",
    "server_sig": "base64-ed25519-signature"
  }
  

Important: the payload contains a human_summary and a tx_id. The wallet reconstructs the actual transaction (or meta‑transaction) from the server using the tx_id via a secure API call if needed, or uses the tx_id as a reference for local signing. Avoid sending raw signed transaction hex.

Encryption flow (server side)

  1. Merchant server fetches the wallet device public key (X25519 public key).
  2. Server generates an ephemeral X25519 key pair for this message.
  3. Derive a shared secret via ECDH: shared = ECDH(ephemeral_priv, device_pub).
  4. Use HKDF(shared, salt) to derive an AES‑GCM key and nonce.
  5. Encrypt the JSON blob with AES‑GCM; attach ephemeral public key and server signature.
  6. Send the encrypted payload (base64) to the messaging gateway along with metadata.

Sample Node.js server snippet (conceptual)


  // Node.js (>=18) conceptual example
  import { generateKeyPairSync, createSecretKey, diffieHellman, randomBytes, createCipheriv } from 'crypto';

  // devicePub is base64 X25519 public key fetched from DB
  const devicePub = Buffer.from(devicePubBase64, 'base64');

  const { publicKey: ephPub, privateKey: ephPriv } = generateKeyPairSync('x25519');
  const shared = diffieHellman({ privateKey: ephPriv, publicKey: devicePub });
  const key = hkdfSha256(shared, salt, info, 32); // implement HKDF
  const iv = randomBytes(12);
  const cipher = createCipheriv('aes-256-gcm', key, iv);
  const ciphertext = Buffer.concat([cipher.update(JSON.stringify(payload)), cipher.final()]);
  const tag = cipher.getAuthTag();

  const envelope = {
    eph_pub: ephPub.export({ type: 'spki', format: 'der' }).toString('base64'),
    iv: iv.toString('base64'),
    tag: tag.toString('base64'),
    ct: ciphertext.toString('base64'),
    server_sig: serverSign(metadata)
  };
  // send envelope to messaging gateway
  

Mobile SDK: receiving and decrypting

The mobile SDK inside the wallet must:

  1. Receive the message via RCS or WebPush.
  2. Use the device's private key (X25519) to compute shared secret with ephemeral public key from envelope.
  3. Derive symmetric key, decrypt with AES‑GCM, verify server signature.
  4. Display the human summary in the native UI and prompt one‑tap approval.

Kotlin pseudocode (Android wallet)


  // Pseudocode: assume KeyPair is generated & private key protected by Android Keystore
  val devicePriv = loadDevicePrivateKey() // KeyStore-protected
  val ephPub = parseBase64(envelope.eph_pub)
  val shared = performX25519(devicePriv, ephPub)
  val aesKey = hkdfSha256(shared, salt, info)
  val plaintext = aesGcmDecrypt(aesKey, envelope.iv, envelope.ct, envelope.tag)
  verifyServerSignature(plaintext, envelope.server_sig)
  showConfirmationUI(plaintext.human_summary)
  

Approval, signing and submission

After the user approves, the wallet builds and locally signs the transaction. Two common patterns exist:

  • Direct submission: Wallet signs and submits to the network (or via a relayer) — server gets callback when confirmed.
  • Relayer + blind signing: Wallet produces a signature over a canonical message and returns the signature to the relayer. The relayer assembles the final transaction using the signature — the server never learns the seed.

Use EIP‑712 typed data or transaction signing formats that support deterministic digesting so the signed digest can be verified and replay‑protected.

RCS specifics and E2EE caveats (2026)

In 2026 many carriers ship MLS‑based E2EE RCS, but not every path is E2EE yet. SDKs must detect RCS capabilities and encryption status before sending sensitive blobs. If you cannot guarantee E2EE, degrade to WebPush or use the “notification-only” SMS/RCS message containing a deep link that triggers a secure in‑app fetch of the confirmation payload.

  • If RCS E2EE is available: send encrypted payload as described.
  • If RCS is available but not E2EE: send only a non‑sensitive prompt & deep link.
  • If RCS unsupported: use WebPush (VAPID + encrypted payload) or FCM/APNS encrypted push, or in‑app polling via TLS.

WebPush fallback — why it’s safe when done right

WebPush supports payload encryption (RFC8291 and friends). When using WebPush as a fallback:

  • Use payload encryption per the standard; derive symmetric keys from subscription info.
  • Authenticate the message with an Ed25519 server signature the wallet can verify.
  • Always avoid delivering raw transaction hex or private keys.

When messaging channels can’t be trusted with encrypted payloads, send a short message with a deep link (URI) that opens the wallet and triggers an authenticated fetch for the full confirmation data. The deep link contains only a short, single‑use request ID — not the transaction itself.


  rcs/message: "Approve your purchase: Tap to review"
  deep_link: mywallet://confirm?req=uuid-1234

  When wallet opens: wallet calls server: GET /confirmations/uuid-1234 (mTLS + access token)
  

Security hardening and operational controls

  • Use KMS/HSM for storing server signing keys and encrypting push tokens. Rotate keys frequently.
  • Token binding & rotation — bind push tokens to device attestation metadata to reduce misuse if tokens leak.
  • Rate limiting for confirmation requests to prevent spam and social engineering.
  • Telemetry & auditing — log only metadata (tx_id, timestamps) and never plaintext transaction contents on the server logs.
  • Consent & revocation — provide users the ability to revoke device authorizations and delete public keys remotely.

Regulatory, privacy, and compliance considerations (2026)

Messaging-based transaction confirmations cross several regulatory lines: payment authentication rules, KYC/AML logs, and data retention laws. Practical controls:

  • Keep minimal personal data in messages; store PII encrypted and with strict retention policies.
  • Implement replay protection and maintain non-repudiation logs for audit trails.
  • Design for regional messaging rules: SMS/US/CA/EU carrier restrictions and lawful intercept requirements may differ — prefer encrypted payloads and minimize message content.

Sample end‑to‑end flow (concise)

  1. User starts purchase in merchant app; merchant creates unsigned transaction and tx_id.
  2. Merchant calls server SDK to send confirmation to the user's registered device public key.
  3. Server encrypts payload using ephemeral X25519 & AES‑GCM and signs metadata with Ed25519 server key.
  4. Message sent via RCS if E2EE available; otherwise WebPush or deep link fallback.
  5. Wallet decrypts, verifies server signature, displays human summary, user taps Approve.
  6. Wallet signs transaction locally and either submits on‑chain or returns signature to relayer.

Developer checklist — what to implement in your SDK

  • Device key generation & attestation APIs (Kotlin/Swift) with secure enclave/KeyStore storage.
  • Server SDK helpers for ephemeral key generation, HKDF, AES‑GCM encryption, and Ed25519 signing.
  • Capability detection for RCS E2EE vs non‑E2EE and automatic fallback logic.
  • Deep link handling and secure in‑app fetch endpoints protected by OAuth/mTLS.
  • Rotation and revocation APIs for device public keys and push tokens.

Advanced strategies and future‑proofing (2026+)

  • Aggregate approvals: batch multiple small confirmations into a single review to reduce UX friction.
  • Threshold approvals: multi‑sig wallets can use the same encrypted confirmation pattern to collect signatures from multiple devices.
  • Use of MLS & provider E2EE: when MLS RCS E2EE is broadly available, integrate MLS session negotiation to avoid per‑message ephemeral keys (though per‑message ephemeral keys still valid for forward secrecy).
  • Post‑quantum readiness: design the envelope structure to allow swapping the KEM to a PQ KEM when libraries stabilize.

Common pitfalls and how to avoid them

  • Do not assume RCS is E2EE: always check capabilities and degrade safely.
  • Never include private keys or mnemonics in messages or in server logs.
  • Avoid long-lived push tokens without rotation; rotate and bind to device attestation.
  • Fail open vs fail closed: prefer failing closed for confirmations that authorize value transfers.
  1. Device registration endpoint: generate key + attestation + store public key.
  2. Server send endpoint: accept tx_id, build human_summary, encrypt envelope, send to gateway.
  3. Mobile SDK handler: receive envelope, decrypt, verify, display, sign locally.
  4. Revocation API: mark device key revoked and invalidate push tokens.

Actionable takeaways

  • Always keep private keys on device. Use ephemeral ECDH + AEAD to encrypt messages for the device.
  • Detect RCS E2EE capability. If E2EE unavailable, send a deep link and perform secure in‑app fetches instead of raw transaction data.
  • Use server signatures. Sign metadata so the wallet can verify the origin without trusting intermediary carriers.
  • Harden tokens and key storage. Use KMS for server secrets and platform attestation for device keys.
  • Plan fallbacks. WebPush/VAPID or in‑app polling are acceptable secure fallbacks when implemented correctly.

Closing thoughts — why this approach wins

As messaging channels converge on stronger E2EE (MLS, carrier upgrades, wider OEM adoption), using RCS and WebPush for transaction confirmations becomes a compelling way to reduce friction. But the security model must be conservative: treat messaging as a transport only, keep secrets in hardware on the wallet, and encrypt every payload end‑to‑end with ephemeral keys and authenticated server metadata.

Call to action

Ready to implement a secure confirmation channel for your NFT checkout or crypto payments? Download our reference SDKs for Node.js and mobile, see a working demo, or request an integration workshop with the nftpay.cloud engineering team. Get hands‑on architecture review and a security checklist tailored to your environment.

Contact us to get a starter repo, production patterns, and a 30‑minute consultation: secure your transaction confirmations without exposing secrets.

Advertisement

Related Topics

#SDK#messaging#mobile
n

nftpay

Contributor

Senior editor and content strategist. Writing about technology, design, and the future of digital media. Follow along for deep dives into the industry's moving parts.

Advertisement
2026-01-25T05:52:47.235Z