Skip to main content

Overview

Rain is a card infrastructure platform that lets you issue credit cards backed by crypto wallets. In this guide, we’ll walk through using Turnkey to create a self-custody wallet, apply for a Rain credit card linked to that wallet, and sign a funding authorization so the card is ready to spend. We’ll use @turnkey/react-wallet-kit for browser-based authentication and wallet creation, and Turnkey’s signMessage to authorize card funding. The user’s keys never leave Turnkey’s secure infrastructure.

Demo: Rain x Turnkey

Watch a walkthrough of creating a self-custody wallet with Turnkey, linking a Rain credit card, and signing a funding authorization.

Getting started

Before you begin, make sure you’ve followed the Turnkey Quickstart guide. You should have: You’ll also need a backend proxy to safely forward requests to the Rain API without exposing your API key to the client.

Install dependencies

npm i @turnkey/react-wallet-kit @turnkey/core react

Setting up the Turnkey wallet

We’ll use @turnkey/react-wallet-kit to authenticate users via email OTP and create a wallet in one step.
"use client";

import { useTurnkey } from "@turnkey/react-wallet-kit";
import { OtpType } from "@turnkey/core";

export default function AuthPage() {
  const {
    initOtp,
    completeOtp,
    createApiKeyPair,
    user,
    wallets,
    signMessage,
  } = useTurnkey();

  async function handleSignup(email: string, name: string) {
    // 1. Send a verification code to the user's email
    const otpId = await initOtp({
      otpType: OtpType.Email,
      contact: email,
    });

    // 2. After the user enters the code, verify and create the wallet
    const publicKey = await createApiKeyPair();

    await completeOtp({
      otpId,
      otpCode: "<user-entered-code>",
      contact: email,
      otpType: OtpType.Email,
      publicKey,
      createSubOrgParams: {
        userName: name,
        userEmail: email,
        subOrgName: email,
        customWallet: {
          walletName: `${name} Wallet`,
          walletAccounts: [
            {
              curve: "CURVE_SECP256K1",
              pathFormat: "PATH_FORMAT_BIP32",
              path: "m/44'/60'/0'/0",
              addressFormat: "ADDRESS_FORMAT_ETHEREUM",
            },
          ],
        },
      },
    });

    // wallets[0].accounts[0].address now contains the new wallet address
  }
}

Setting up the Rain API proxy

Rain API calls require a server-side API key. Create an Express proxy to forward requests securely:
import express from "express";

const app = express();
app.use(express.json());

const RAIN_BASE = "https://api.raincards.xyz/v1";
const API_KEY = process.env.RAIN_API_KEY!;

async function rainFetch(path: string, body: object) {
  const res = await fetch(`${RAIN_BASE}${path}`, {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      "Api-Key": API_KEY,
    },
    body: JSON.stringify(body),
  });
  return { status: res.status, data: await res.json() };
}

// Submit consumer application
app.post("/api/rain/application", async (req, res) => {
  const result = await rainFetch("/issuing/applications/user", req.body);
  res.status(result.status).json(result.data);
});

// Create virtual card for a user
app.post("/api/rain/users/:userId/cards", async (req, res) => {
  const result = await rainFetch(
    `/issuing/users/${req.params.userId}/cards`,
    req.body
  );
  res.status(result.status).json(result.data);
});

Applying for a Rain card

With the wallet created, submit a consumer application to Rain and then issue a virtual card:
const walletAddress = wallets[0].accounts[0].address;

// 1. Submit the consumer application
const appRes = await fetch("/api/rain/application", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({
    firstName: "Alex",
    lastName: "Demo",
    birthDate: "1990-01-15",
    email: "alex@example.com",
    walletAddress,
    // ... additional required fields
  }),
});

const { id: userId } = await appRes.json();

// 2. Issue a virtual credit card
const cardRes = await fetch(`/api/rain/users/${userId}/cards`, {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ type: "virtual" }),
});

const card = await cardRes.json();
// card.last4, card.expirationMonth, card.expirationYear

Signing a funding authorization

To fund the card, use Turnkey’s signMessage to sign an authorization payload from the user’s wallet. The signature proves the wallet owner has approved the transfer. No private key ever leaves Turnkey’s infrastructure.
const account = wallets[0].accounts[0];

const message = JSON.stringify({
  action: "authorize_funding",
  amount: 100,
  currency: "rUSD",
  wallet: account.address,
  timestamp: new Date().toISOString(),
});

const signature = await signMessage({
  message,
  walletAccount: account,
});

// signature.r, signature.s, signature.v
// Submit this signature to your backend to complete the funding

Summary

✅ You’ve now learned how to:
  • Authenticate users and create Turnkey wallets via email OTP using @turnkey/react-wallet-kit
  • Submit a consumer application to Rain and issue a virtual credit card linked to the wallet
  • Sign a funding authorization with signMessage so the card is ready to spend
  • Keep Rain API keys secure behind a server-side proxy