# Agent Checkouts
Source: https://docs.crossmint.com/agents/agent-checkouts-quickstart
Get your agent buying from any merchant URL in 15 minutes.
Let your agent buy from any merchant with a single API. Hand the **Agent Checkouts API** a product URL and a spending cap, and Crossmint completes the checkout on your user's behalf — paying with any method the merchant accepts.
By the end you will have a script that saves a buyer profile, creates a checkout from a product URL, polls it through its lifecycle, embeds the live browser session for the user to watch, answers each pending action, and reads the final receipt.
See the full flow in action without setting anything up locally.
Explore the full reference implementation this guide is based on.
Agent Checkouts can only be tested in **production** today — there is no staging environment for it. Use a `ck_production_...` key and **live** auth credentials; test/staging credentials fail with `401`.
## How it works
A checkout walks a simple lifecycle with **no webhooks in v1** — you poll until it reaches a terminal state:
1. **Create** a checkout and get back an `id`.
2. **Poll** `GET /{id}` (≈ every 1.5s) as it walks the lifecycle.
3. **Embed** the live browser session in an `
}
appearance={{
spacingUnit: "8px",
borderRadius: "12px",
colors: {
inputBackground: "#fffdf9",
buttonBackground: "#fffaf2",
border: "#835911",
background: "#FAF5EC",
textPrimary: "#5f2c1b",
textSecondary: "#835911",
textLink: "#1400cb",
danger: "#ff3333",
accent: "#602C1B",
},
}}
>
{children}
```
### Modal props
| Prop | Type | Description |
| -------------------- | --------------------- | -------------------------------------------------------------------- |
| `authModalTitle` | `string` | Custom title displayed at the top of the authentication modal |
| `termsOfServiceText` | `string \| ReactNode` | Custom terms of service text displayed below the authentication form |
| `appearance` | `UIConfig` | Styling configuration for the modal (see example above) |
The modal will also use your display name configured in the Crossmint Console. To learn more, see the "Email Customization" section below.
## Embedded Login
For a more integrated experience, you can use our embedded login component, which offers flexibility to display the login form in your own modal or as part of a split login screen.
```tsx theme={null}
import { EmbeddedAuthForm, useAuth } from "@crossmint/client-sdk-react-ui";
export default function Home() {
const { user, status } = useAuth(); // "in-progress" | "logged-in" | "logged-out"
return
{status === "logged-in" ? user?.email : }
;
}
```
## Email Customization
Email OTP is a login method that allows users to sign in to your app using their email address. They receive a one-time code via email that they can use to log in.
You can customize the email template to align with your brand's identity. We **strongly recommend** doing so, as it increases user trust and security.
To modify the email template:
1. In the Crossmint Console, click on Settings, and navigate to the **Branding** tab.
2. Here, you can customize:
* The **logo** displayed in the email with your logo.
* The **display name** textbox to include your brand's name.
When customizing email text, avoid using terms like "airdrop", "token", or "crypto" as these can trigger spam
filters and hurt email deliverability.
# Introduction
Source: https://docs.crossmint.com/authentication/introduction
Authenticate users and register customer identities for regulated activities
Crossmint provides two complementary capabilities for managing your users:
* **Identity** — Link a `userLocator` to additional customer information (KYC data, documents) required for regulated activities such as onramp, offramp, and payouts.
* **Auth** — Link a `userLocator` to an authentication system (Crossmint's built-in auth or your own). This lets users sign in, authorize API calls, and access Crossmint products.
Choose the path that matches your needs, or use both together.
# Login Methods
Source: https://docs.crossmint.com/authentication/login-methods
Customize how users can log in to your app
Crossmint Auth supports the following login methods:
* **Email OTP**: passwordless sign-in using a one time code delivered to the user's email.
* **Social Accounts**: Sign in with Google, X, Farcaster, and more.
* **External wallets**: connect with crypto wallets for Web3 authentication. Use `"web3"` for all wallets, `"web3:evm-only"` for Ethereum-compatible wallets only, or `"web3:solana-only"` for Solana wallets only.
To customize which login methods are shown to your users, use the `loginMethods` prop when initializing Crossmint Auth.
By default, only email and Google are enabled.
```tsx app/providers/Providers.tsx theme={null}
"use client";
import { CrossmintProvider, CrossmintAuthProvider } from "@crossmint/client-sdk-react-ui";
export default function Providers({ children }: { children: React.ReactNode }) {
return (
{children}
);
}
```
# Overview
Source: https://docs.crossmint.com/authentication/overview
Authenticate users into your app with Crossmint Auth or your own provider
Crossmint Auth is a quick-to-integrate authentication solution, ideal for staging environments, demos, and prototyping. It lets you authenticate users using web3 or traditional sign-in methods:
* **Email OTP**: passwordless sign-in using a one time code delivered to the user's email.
* **Social Accounts**: Sign in with Google, Apple, X, and more.
* **Farcaster**: using the [Sign In With Farcaster (SIWF) standard](https://github.com/farcasterxyz/protocol/discussions/110).
* **External wallets**: connect with crypto wallets for Web3 authentication.
### When to Use Crossmint Auth
| Scenario | Recommendation |
| --------------------------- | --------------------------------------------------------------------------------------------------------- |
| **Staging / prototyping** | Use Crossmint Auth. Get up and running in minutes |
| **Demos and quickstarts** | Use Crossmint Auth. Minimal setup, great for testing |
| **Production applications** | [Bring your own auth provider](/wallets/guides/bring-your-own-auth) for full control over user management |
### Key Characteristics
Authorize calls to your backend services and any Crossmint product.
Unified identities across your backend and the blockchain.
Launch your app in under 5 minutes.
### Get Started with Auth
Get started with Crossmint Auth in a React web app in under 5 minutes.
Get started with Crossmint Auth in an Expo app in under 5 minutes.
Recommended for production. Use Auth0, Firebase, Supabase, or any JWT-based provider with Crossmint.
Detailed wallet creation guide.
Get and update user information.
Contact our sales team for advanced support.
# Flutter
Source: https://docs.crossmint.com/authentication/quickstarts/flutter
Authenticate users in your Flutter app in under 5 minutes
Crossmint Auth on Flutter is **headless** — there is no pre-built login modal.
You build your own UI and call the SDK directly. This guide walks through both
**email OTP** and **OAuth** login flows. Optional pre-built widgets are available
via `crossmint_flutter_ui.dart`.
See a full working example with auth and wallets.
Install the Crossmint Flutter SDK:
The SDK bundles the following dependencies:
| Package | Purpose |
| ------------------------ | -------------------------------------------------------------- |
| `flutter_secure_storage` | Encrypted token storage (Keychain on iOS, Keystore on Android) |
| `url_launcher` | Opens the OAuth consent screen in a system browser |
| `app_links` | Listens for OAuth deep-link redirects back to your app |
OAuth redirects require a **deep link scheme** so the browser can return to your app after login.
**Android** — add to your `AndroidManifest.xml`:
```xml android/app/src/main/AndroidManifest.xml theme={null}
```
**iOS** — add to your `Info.plist`:
```xml ios/Runner/Info.plist theme={null}
CFBundleURLTypesCFBundleURLSchemesmyapp
```
Pass the same scheme to `CrossmintClientConfig.appScheme`.
Initialize `CrossmintClient` and restore any persisted session on app launch.
```dart lib/crossmint.dart theme={null}
import 'package:crossmint_flutter/crossmint_client.dart';
import 'package:crossmint_flutter/crossmint_flutter_auth.dart';
/// Shared Crossmint client instance — import this file from your screens.
final client = CrossmintClient(
config: CrossmintClientConfig(
apiKey: const String.fromEnvironment('CROSSMINT_API_KEY'),
appScheme: 'myapp',
),
);
```
```dart main.dart theme={null}
import 'package:flutter/material.dart';
import 'package:crossmint_flutter/crossmint_flutter_auth.dart';
import 'crossmint.dart'; // shared client instance
Future main() async {
WidgetsFlutterBinding.ensureInitialized();
await client.initialize();
await client.auth.restoreSession();
// Start the OAuth callback router
final authRouter = CrossmintAuthCallbackRouter(auth: client.auth);
await authRouter.start();
runApp(const MyApp());
}
```
Pass the API key via `--dart-define` when running the app:
```bash theme={null}
flutter run --dart-define=CROSSMINT_API_KEY=YOUR_CLIENT_API_KEY
```
Use the `CrossmintAuthClient` to implement email OTP and OAuth login flows.
### Email OTP Login
```dart login_screen.dart theme={null}
import 'package:flutter/material.dart';
import 'package:crossmint_flutter/crossmint_flutter_auth.dart';
import 'crossmint.dart'; // shared client instance
class LoginScreen extends StatefulWidget {
const LoginScreen({super.key});
@override
State createState() => _LoginScreenState();
}
class _LoginScreenState extends State {
final _emailController = TextEditingController();
final _otpController = TextEditingController();
String? _emailId;
bool _otpSent = false;
bool _isPending = false;
Future _sendOtp() async {
final email = _emailController.text.trim();
if (email.isEmpty) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Enter a valid email address')),
);
return;
}
setState(() => _isPending = true);
try {
final challenge = await client.auth.sendEmailOtp(email);
setState(() {
_emailId = challenge.id;
_otpSent = true;
});
} catch (e) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Failed to send OTP: $e')),
);
} finally {
setState(() => _isPending = false);
}
}
Future _verifyOtp() async {
final otp = _otpController.text.trim();
if (otp.isEmpty) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Enter the OTP code')),
);
return;
}
setState(() => _isPending = true);
try {
await client.auth.confirmEmailOtp(
email: _emailController.text.trim(),
emailId: _emailId!,
token: otp,
);
} catch (e) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Invalid OTP: $e')),
);
} finally {
setState(() => _isPending = false);
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Padding(
padding: const EdgeInsets.all(20),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
TextField(
controller: _emailController,
decoration: const InputDecoration(
hintText: 'Enter your email',
border: OutlineInputBorder(),
),
enabled: !_otpSent,
),
const SizedBox(height: 10),
if (!_otpSent)
SizedBox(
width: double.infinity,
child: ElevatedButton(
onPressed: _isPending ? null : _sendOtp,
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF05B959),
),
child: _isPending
? const SizedBox(
height: 20, width: 20,
child: CircularProgressIndicator(
color: Colors.white, strokeWidth: 2,
),
)
: const Text('Send OTP',
style: TextStyle(color: Colors.white)),
),
)
else ...[
TextField(
controller: _otpController,
decoration: const InputDecoration(
hintText: 'Enter OTP code',
border: OutlineInputBorder(),
),
),
const SizedBox(height: 10),
SizedBox(
width: double.infinity,
child: ElevatedButton(
onPressed: _isPending ? null : _verifyOtp,
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF05B959),
),
child: _isPending
? const SizedBox(
height: 20, width: 20,
child: CircularProgressIndicator(
color: Colors.white, strokeWidth: 2,
),
)
: const Text('Verify OTP',
style: TextStyle(color: Colors.white)),
),
),
],
],
),
),
);
}
@override
void dispose() {
_emailController.dispose();
_otpController.dispose();
super.dispose();
}
}
```
### OAuth Login (Google, Twitter)
```dart oauth_button.dart theme={null}
import 'package:flutter/material.dart';
import 'package:crossmint_flutter/crossmint_flutter_auth.dart';
import 'crossmint.dart'; // shared client instance
class GoogleSignInButton extends StatelessWidget {
const GoogleSignInButton({super.key});
@override
Widget build(BuildContext context) {
return SizedBox(
width: double.infinity,
child: ElevatedButton(
onPressed: () => client.auth.loginWithOAuth(
CrossmintOAuthProvider.google,
),
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF4285F4),
),
child: const Text(
'Sign in with Google',
style: TextStyle(color: Colors.white),
),
),
);
}
}
```
The SDK handles platform differences automatically via `CrossmintAuthCallbackRouter`:
* **iOS**: Uses `url_launcher` to open an in-app authentication session. The redirect URL is
captured by the auth callback router via `app_links`.
* **Android**: Opens the system browser. The OAuth provider redirects back to your app via the
deep link scheme configured in `AndroidManifest.xml`.
The `CrossmintAuthCallbackRouter` listens for incoming deep links on both platforms and automatically
completes the OAuth flow — no manual URL handling required.
```dart home_screen.dart theme={null}
import 'package:flutter/material.dart';
import 'package:crossmint_flutter/crossmint_flutter_auth.dart';
import 'crossmint.dart'; // shared client instance
class HomeScreen extends StatelessWidget {
const HomeScreen({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
body: ValueListenableBuilder(
valueListenable: client.auth.stateListenable,
builder: (context, state, _) {
final user = state.user;
if (user == null) return const LoginScreen();
return Padding(
padding: const EdgeInsets.all(20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Text('Welcome!',
style: TextStyle(fontSize: 20)),
TextButton(
onPressed: () => client.auth.logout(),
child: const Text('Logout',
style: TextStyle(color: Colors.red)),
),
],
),
const SizedBox(height: 20),
Text('Email: ${user.email}'),
Text('User ID: ${user.id}'),
],
),
);
},
),
);
}
}
```
## Auth State Values
The `CrossmintAuthState` provides the following status information:
| Status | Description |
| -------------- | -------------------------------------------------------------- |
| `initializing` | The SDK is loading stored tokens and checking session validity |
| `loggedOut` | No active session. Show your login screen |
| `inProgress` | An OAuth or OTP flow is currently in progress |
| `loggedIn` | The user is authenticated. `user` and `jwt` are available |
## Token Storage
By default, the SDK stores authentication tokens using `flutter_secure_storage`, which encrypts data
at rest using the platform keychain (iOS) or keystore (Android). Tokens persist across app restarts
and are protected by the device's hardware-backed security.
You can provide a custom storage implementation via `CrossmintClientConfig.authStorage` if you need
a different storage backend. The SDK also provides `InMemoryCrossmintAuthStorage` for testing.
## Differences from the React Native SDK
| Feature | React Native | Flutter |
| -------------------- | ------------------------------------------ | ------------------------------------------------------------------- |
| **Login UI** | Headless — build your own UI | Headless — build your own UI (optional pre-built widgets available) |
| **State management** | React hooks (`useCrossmintAuth`) | `ChangeNotifier` / `ValueListenable` |
| **OAuth flow** | System browser via `expo-web-browser` | System browser via `url_launcher` |
| **OAuth callback** | `Linking.useURL()` + `createAuthSession()` | `CrossmintAuthCallbackRouter` (automatic) |
| **Token storage** | `expo-secure-store` (encrypted) | `flutter_secure_storage` (encrypted) |
| **Package** | `@crossmint/client-sdk-react-native-ui` | `crossmint_flutter` |
| **Deep link setup** | `scheme` in `app.json` | `AndroidManifest.xml` + `Info.plist` |
| **OTP prompts** | Manual via `useWalletOtpSigner` hook | Automatic via `CrossmintOtpSignerListener` widget |
## Moving to Production
**Crossmint Auth is designed for staging and getting started quickly.** For production applications,
Crossmint strongly recommends migrating to your own authentication provider for full control
over user management.
When you are ready to go to production, Crossmint recommends:
1. **Set up your own auth provider** (Auth0, Firebase, Supabase, Stytch, etc.) and follow the [Bring Your Own Auth guide](/wallets/guides/bring-your-own-auth) to integrate it with Crossmint via JWT.
2. Create a developer account on the production console.
3. Create a production client API key on the API Keys page with the API scopes `users.create`, `users.read`, `wallets.read`.
4. [Configure JWT authentication](/introduction/platform/api-keys/jwt-authentication) for your auth provider in the Crossmint Console.
## Next Steps
Install and configure the Flutter SDK, then explore the full API reference
Recommended for production — integrate your own auth provider via JWT
Get notified when a user signs up or updates their profile
Provision wallets automatically when users sign up
# React
Source: https://docs.crossmint.com/authentication/quickstarts/react
Sign up your first user in 5 minutes with React
See a full working example with auth and wallets.
Run the following command to install the SDK:
Add the necessary Crossmint providers to your app.
```tsx next.js theme={null}
"use client";
import {
CrossmintProvider,
CrossmintAuthProvider,
} from "@crossmint/client-sdk-react-ui";
export function Providers({ children }: { children: React.ReactNode }) {
return (
{children}
);
}
```
```tsx next.js theme={null}
"use client";
import { useAuth } from "@crossmint/client-sdk-react-ui";
export function AuthButton() {
const { login, logout, user, jwt } = useAuth();
return (
);
}
```
## Moving to Production
**Crossmint Auth is designed for staging and getting started quickly.** For production applications, Crossmint strongly recommends migrating to your own authentication provider for full control over user management.
When you are ready to go to production, Crossmint recommends:
1. **Set up your own auth provider** (Auth0, Firebase, Supabase, Stytch, etc.) and follow the [Bring Your Own Auth guide](/wallets/guides/bring-your-own-auth) to integrate it with Crossmint via JWT.
2. Create a developer account on the production console.
3. Create a production client API key on the API Keys page with the API scopes `users.create`, `users.read`, `wallets.read`.
4. [Configure JWT authentication](/introduction/platform/api-keys/jwt-authentication) for your auth provider in the Crossmint Console.
## Next Steps
Recommended for production — integrate your own auth provider via JWT
Get notified when a user signs up or updates their profile
Provision wallets automatically when users sign up
# React Native
Source: https://docs.crossmint.com/authentication/quickstarts/react-native
Authenticate users in your Expo app in under 5 minutes
Crossmint Auth on React Native is **headless** — there is no pre-built login modal.
You build your own UI and call the SDK hooks directly. This guide walks through both
**email OTP** and **OAuth** login flows.
See a full working example with auth and wallets.
Install the Crossmint React Native SDK and its required Expo peer dependencies:
The SDK requires the following Expo packages:
| Package | Purpose |
| ------------------- | -------------------------------------------------------------- |
| `expo-secure-store` | Encrypted token storage (Keychain on iOS, Keystore on Android) |
| `expo-web-browser` | Opens the OAuth consent screen in a system browser |
| `expo-device` | Detects device capabilities for secure storage availability |
| `expo-linking` | Listens for OAuth deep-link redirects back to your app |
OAuth redirects require a **deep link scheme** so the browser can return to your app after login.
Add a `scheme` to your `app.json` (or `app.config.ts`):
```json app.json theme={null}
{
"expo": {
"scheme": "myapp"
}
}
```
**Expo Go** uses a different scheme at runtime (`exp://127.0.0.1:8081`). The SDK
detects Expo Go automatically and adjusts the redirect URI, so OAuth works during
development without extra configuration. For production builds, the `scheme` value
above is used.
Wrap your application with `CrossmintProvider` and `CrossmintAuthProvider`.
```tsx providers.tsx theme={null}
import {
CrossmintProvider,
CrossmintAuthProvider,
} from "@crossmint/client-sdk-react-native-ui";
type ProvidersProps = {
children: React.ReactNode;
};
export default function Providers({ children }: ProvidersProps) {
return (
{children}
);
}
```
| Prop | Type | Description |
| ----------------- | -------------------- | ---------------------------------------------------------------------------- |
| `appSchema` | `string \| string[]` | Override the deep link scheme. Defaults to the `scheme` in your Expo config. |
| `storageProvider` | `StorageProvider` | Custom token storage implementation. Defaults to `expo-secure-store`. |
| `onLoginSuccess` | `(user) => void` | Callback fired after a successful login. |
Use the `useCrossmintAuth` hook to access auth functions. Unlike the React web SDK, there is no
pre-built modal — you call `loginWithOAuth` and `crossmintAuth.sendEmailOtp` / `crossmintAuth.confirmEmailOtp` directly.
### Email OTP Login
```tsx LoginScreen.tsx theme={null}
import React, { useState } from "react";
import {
View,
Text,
TextInput,
TouchableOpacity,
ActivityIndicator,
Alert,
} from "react-native";
import { useCrossmintAuth } from "@crossmint/client-sdk-react-native-ui";
export default function LoginScreen() {
const { crossmintAuth, createAuthSession, status } =
useCrossmintAuth();
const [email, setEmail] = useState("");
const [emailId, setEmailId] = useState("");
const [otp, setOtp] = useState("");
const [otpSent, setOtpSent] = useState(false);
const [isPending, setIsPending] = useState(false);
const sendOtp = async () => {
if (!email.trim()) {
Alert.alert("Error", "Enter a valid email address");
return;
}
setIsPending(true);
try {
const res =
await crossmintAuth?.sendEmailOtp(email);
if (res == null) {
throw new Error("Auth client not ready");
}
setEmailId(res.emailId);
setOtpSent(true);
} catch (error) {
const message =
error instanceof Error
? error.message
: "Failed to send OTP.";
Alert.alert("Error", message);
} finally {
setIsPending(false);
}
};
const verifyOtp = async () => {
if (!otp.trim()) {
Alert.alert("Error", "Enter the OTP code");
return;
}
setIsPending(true);
try {
const oneTimeSecret =
await crossmintAuth?.confirmEmailOtp(
email,
emailId,
otp
);
if (oneTimeSecret == null) {
throw new Error("Auth client not ready");
}
await createAuthSession(oneTimeSecret);
} catch (error) {
const message =
error instanceof Error
? error.message
: "Invalid OTP. Try again.";
Alert.alert("Error", message);
} finally {
setIsPending(false);
}
};
if (status === "initializing") {
return (
);
}
return (
{!otpSent ? (
{isPending ? (
) : (
Send OTP
)}
) : (
<>
{isPending ? (
) : (
Verify OTP
)}
>
)}
);
}
```
### OAuth Login (Google, Twitter)
```tsx OAuthButton.tsx theme={null}
import React, { useEffect } from "react";
import { TouchableOpacity, Text } from "react-native";
import { useCrossmintAuth } from "@crossmint/client-sdk-react-native-ui";
import * as Linking from "expo-linking";
export default function OAuthButton() {
const { loginWithOAuth, createAuthSession } =
useCrossmintAuth();
const url = Linking.useURL();
useEffect(() => {
if (url != null) {
createAuthSession(url);
}
}, [url, createAuthSession]);
return (
loginWithOAuth("google")}
>
Sign in with Google
);
}
```
The SDK handles platform differences automatically:
* **iOS**: Uses `WebBrowser.openAuthSessionAsync`, which opens an in-app authentication
session (ASWebAuthenticationSession). The redirect URL is intercepted by the authentication
session before it reaches your app's URL routing, so `Linking.useURL()` remains `null` after
the OAuth flow. There is no double invocation — `createAuthSession` is called only once by
the SDK internally.
* **Android**: Uses `WebBrowser.openBrowserAsync`, which opens the system browser. The OAuth
provider redirects back to your app via the deep link scheme. You must listen for the
incoming URL with `Linking.useURL()` and pass it to `createAuthSession` yourself, as shown
in the example above.
The `Linking.useURL()` + `createAuthSession` pattern works on both platforms, so a single
code path is sufficient.
```tsx HomeScreen.tsx theme={null}
import React from "react";
import { View, Text, TouchableOpacity } from "react-native";
import { useCrossmintAuth } from "@crossmint/client-sdk-react-native-ui";
export default function HomeScreen() {
const { user, jwt, logout, status } = useCrossmintAuth();
return (
Welcome!LogoutEmail: {user?.email}User ID: {user?.id}Status: {status}JWT: {jwt ? "Present" : "None"}
);
}
```
## Auth Status Values
The `status` field returned by `useCrossmintAuth` can be one of the following:
| Status | Description |
| ---------------- | -------------------------------------------------------------- |
| `"initializing"` | The SDK is loading stored tokens and checking session validity |
| `"logged-out"` | No active session. Show your login screen |
| `"in-progress"` | An OAuth or OTP flow is currently in progress |
| `"logged-in"` | The user is authenticated. `user` and `jwt` are available |
## Token Storage
By default, the SDK stores authentication tokens using `expo-secure-store`, which encrypts data
at rest using the platform keychain (iOS) or keystore (Android). This means tokens persist
across app restarts and are protected by the device's hardware-backed security.
You can provide a custom storage implementation via the `storageProvider` prop on
`CrossmintAuthProvider` if you need a different storage backend.
## Differences from the React Web SDK
| Feature | React (Web) | React Native |
| -------------------- | -------------------------------- | ------------------------------------------------------ |
| **Login UI** | Pre-built modal via `login()` | Headless — build your own UI |
| **Auth hook** | `useAuth()` | `useCrossmintAuth()` |
| **OAuth flow** | Popup / redirect in browser | System browser via `expo-web-browser` |
| **OAuth callback** | Handled automatically | Listen with `Linking.useURL()` + `createAuthSession()` |
| **Token storage** | Browser `localStorage` / cookies | `expo-secure-store` (encrypted) |
| **Package** | `@crossmint/client-sdk-react-ui` | `@crossmint/client-sdk-react-native-ui` |
| **Deep link scheme** | Not required | Required in `app.json` for OAuth |
| **Email OTP** | Handled by modal | Call `sendEmailOtp` / `confirmEmailOtp` manually |
## Moving to Production
**Crossmint Auth is designed for staging and getting started quickly.** For production applications,
Crossmint strongly recommends migrating to your own authentication provider for full control
over user management.
When you are ready to go to production, Crossmint recommends:
1. **Set up your own auth provider** (Auth0, Firebase, Supabase, Stytch, etc.) and follow the [Bring Your Own Auth guide](/wallets/guides/bring-your-own-auth) to integrate it with Crossmint via JWT.
2. Create a developer account on the production console.
3. Create a production client API key on the API Keys page with the API scopes `users.create`, `users.read`, `wallets.read`.
4. [Configure JWT authentication](/introduction/platform/api-keys/jwt-authentication) for your auth provider in the Crossmint Console.
## Next Steps
* [Create Smart Wallets on sign up](/wallets/quickstarts/react-native) using the wallets React Native quickstart
* [Bring Your Own Auth](/wallets/guides/bring-your-own-auth), recommended for production
* Read and update [user information](/authentication/user-profile)
* Use [webhooks](/authentication/webhooks) to get notified when a user signs up
* [React Native SDK Reference](/sdk-reference/wallets/react-native/get-started) for the full API documentation
# Secure Cookies
Source: https://docs.crossmint.com/authentication/security
Learn how to securely store Crossmint Auth cookies in your application
Authentication tokens are accessible to client-side JavaScript by default through non-HttpOnly cookies. For stronger security, you can store tokens in HttpOnly cookies, which are accessible only on the server side. This setup requires custom routes for refreshing tokens and logging out, using utilities from `@crossmint/server-sdk`.
## 1. Configure Cookie Options
When initializing the server SDK, configure secure cookie options:
```typescript theme={null}
import { createCrossmint, CrossmintAuth } from "@crossmint/server-sdk";
const crossmint = createCrossmint({ apiKey: process.env.SERVER_CROSSMINT_API_KEY });
const crossmintAuth = CrossmintAuth.from(crossmint, {
cookieOptions: {
httpOnly: true,
secure: true, // Only send cookies over HTTPS
domain: ".yourdomain.com", // Optional: specify cookie domain
},
});
```
**Note:** The `httpOnly` flag only applies to the refresh token. The session JWT remains accessible to client-side JavaScript since it's needed for API calls.
## 2. Custom Routes Implementation
### Token Refresh Route
```typescript theme={null}
import { createCrossmint, CrossmintAuth } from "@crossmint/server-sdk";
import { NextRequest } from "next/server";
const crossmint = createCrossmint({ apiKey: process.env.SERVER_CROSSMINT_API_KEY! });
const crossmintAuth = CrossmintAuth.from(crossmint);
export async function POST(request: NextRequest) {
return await crossmintAuth.handleCustomRefresh(request);
}
```
```typescript theme={null}
import express from "express";
import { createCrossmint, CrossmintAuth } from "@crossmint/server-sdk";
const app = express()
const crossmint = createCrossmint({ apiKey: process.env.SERVER_CROSSMINT_API_KEY! });
const crossmintAuth = CrossmintAuth.from(crossmint);
app.post("/api/auth/refresh", async (req, res) => {
await crossmintAuth.handleCustomRefresh(req, res);
res.end();
});
```
### Logout Route
```typescript theme={null}
import { createCrossmint, CrossmintAuth } from "@crossmint/server-sdk";
import { NextRequest } from "next/server";
const crossmint = createCrossmint({ apiKey: process.env.SERVER_CROSSMINT_API_KEY! });
const crossmintAuth = CrossmintAuth.from(crossmint);
export async function POST(request: NextRequest) {
return await crossmintAuth.logout(request);
}
```
```typescript theme={null}
import express from "express";
import { createCrossmint, CrossmintAuth } from "@crossmint/server-sdk";
const app = express()
const crossmint = createCrossmint({ apiKey: process.env.SERVER_CROSSMINT_API_KEY! });
const crossmintAuth = CrossmintAuth.from(crossmint);
app.post("/api/auth/logout", async (req, res) => {
await crossmintAuth.logout(req, res);
res.end();
});
```
## 3. Client Configuration
Configure the client SDK to use your custom routes:
```typescript theme={null}
import { createCrossmint, CrossmintAuth } from "@crossmint/client-sdk-auth";
const crossmint = createCrossmint({ apiKey: process.env.NEXT_PUBLIC_CLIENT_CROSSMINT_API_KEY! });
const crossmintAuth = CrossmintAuth.from(crossmint, {
refreshRoute: "/api/auth/refresh",
logoutRoute: "/api/auth/logout"
});
```
```tsx theme={null}
import { CrossmintProvider, CrossmintAuthProvider } from "@crossmint/client-sdk-react-ui";
{children}
```
**Note:** Depending on the framework you're using, you might need to set the whole URL in the `refreshRoute` and `logoutRoute` options.
# Server-Side Rendering (SSR)
Source: https://docs.crossmint.com/authentication/ssr
Integrate Crossmint Auth on the server-side for user authentication and management
Crossmint Auth provides a flexible and simple authentication solution for your crypto server-side applications. This guide covers how to integrate and use Crossmint Auth across various server-side frameworks.
## Overview
Our server SDK allows you to:
* Manage user sessions
* Retrieve user profiles
* Verify JSON Web Tokens (JWTs)
## Installation
First, install the Crossmint Server SDK:
```bash npm theme={null}
npm i @crossmint/server-sdk
```
```bash yarn theme={null}
yarn add @crossmint/server-sdk
```
```bash pnpm theme={null}
pnpm add @crossmint/server-sdk
```
```bash bun theme={null}
bun add @crossmint/server-sdk
```
## Initialization
To use Crossmint Auth, you need to initialize it with your Server API key. This API requires the `users.read` scope.
```typescript theme={null}
import { createCrossmint, CrossmintAuth } from "@crossmint/server-sdk";
const crossmint = createCrossmint({ apiKey: process.env.SERVER_CROSSMINT_API_KEY });
const crossmintAuth = CrossmintAuth.from(crossmint);
```
## Core Functionality
### Session Management
The `getSession` method validates or refreshes a user's session based on their JWT and refresh token.
```typescript theme={null}
const { jwt, refreshToken, userId } = await crossmintAuth.getSession(req, res);
```
This method:
1. Fetches the current JWT and refresh token from the cookies with keys `crossmint-jwt` and `crossmint-refresh-token`.
2. Checks if the current JWT is valid
3. Refreshes the session if needed
4. Stores the new JWT and refresh token in cookies
5. Returns new auth materials and the user ID
For other frameworks that do not expose standard request and response objects, such as Next.js using the App Router, you can pass in an object with `jwt` and `refreshToken` properties instead:
```typescript middleware.ts theme={null}
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
import { createCrossmint, CrossmintAuth } from "@crossmint/server-sdk";
export async function middleware(request: NextRequest) {
// Skip middleware for API routes and static files
if (request.nextUrl.pathname.startsWith("/api") || request.nextUrl.pathname.startsWith("/_next")) {
return NextResponse.next();
}
const response = NextResponse.next();
const jwt = request.cookies.get("crossmint-jwt")?.value;
const refreshToken = request.cookies.get("crossmint-refresh-token")?.value;
if (refreshToken == null) {
return response;
}
try {
const crossmint = createCrossmint({
apiKey: process.env.SERVER_CROSSMINT_API_KEY || "",
});
const crossmintAuth = CrossmintAuth.from(crossmint);
const { jwt: newJwt, refreshToken: newRefreshToken } = await crossmintAuth.getSession({
jwt,
refreshToken,
});
// Only update response cookies if tokens have changed
if (newJwt !== jwt || newRefreshToken.secret !== refreshToken) {
response.cookies.set("crossmint-jwt", newJwt);
response.cookies.set("crossmint-refresh-token", newRefreshToken.secret);
}
} catch (_) {
// If auth fails, clear cookies and redirect to home
response.cookies.delete("crossmint-jwt");
response.cookies.delete("crossmint-refresh-token");
}
return response;
}
```
```typescript getAuthSession.ts theme={null}
import { cookies } from "next/headers";
import { createCrossmint, CrossmintAuth } from "@crossmint/server-sdk";
export async function getAuthSession() {
const cookieStore = await cookies();
const jwt = cookieStore.get("crossmint-jwt")?.value;
const refreshToken = cookieStore.get("crossmint-refresh-token")?.value;
if (refreshToken == null) {
return;
}
try {
const crossmint = createCrossmint({
apiKey: process.env.SERVER_CROSSMINT_API_KEY || "",
});
const crossmintAuth = CrossmintAuth.from(crossmint);
const session = await crossmintAuth.getSession({
jwt,
refreshToken,
});
return session;
} catch (_) {
return;
}
}
```
```typescript page.tsx theme={null}
import { getAuthSession } from "@/hooks/auth";
import { createCrossmint, CrossmintAuth } from "@crossmint/server-sdk";
export default async function ProtectedRoute() {
const session = await getAuthSession();
const userId = session?.userId;
if (userId != null) {
// Fetch user data or perform authorized actions
const crossmint = createCrossmint({
apiKey: process.env.SERVER_CROSSMINT_API_KEY || "",
});
const crossmintAuth = CrossmintAuth.from(crossmint);
const userData = await crossmintAuth.getUser(userId);
return
Welcome, {userData.email}!
;
}
// Handle unauthenticated state
return
Please log in to access this page.
;
}
```
For Express.js applications, you can create middleware to handle authentication:
```typescript theme={null}
import express from "express";
import { createCrossmint, CrossmintAuth } from "@crossmint/server-sdk";
const app = express();
const crossmint = createCrossmint({
apiKey: process.env.SERVER_CROSSMINT_API_KEY || "",
});
const crossmintAuth = CrossmintAuth.from(crossmint);
const authMiddleware = async (req, res, next) => {
try {
const { jwt, refreshToken, userId } = await crossmintAuth.getSession(req, res);
req.user = { userId };
next();
} catch (error) {
res.status(401).json({ error: "Authentication failed" });
}
};
app.use(authMiddleware);
app.get("/protected", (req, res) => {
res.json({ message: "Protected route", userId: req.user.userId });
});
```
For a basic Node.js server:
```typescript theme={null}
import http from "http";
import { createCrossmint, CrossmintAuth } from "@crossmint/server-sdk";
const crossmint = createCrossmint({
apiKey: process.env.SERVER_CROSSMINT_API_KEY || "",
});
const crossmintAuth = CrossmintAuth.from(crossmint);
const server = http.createServer(async (req, res) => {
if (req.url === "/protected") {
try {
const { userId } = await crossmintAuth.getSession(req, res);
const userData = await crossmintAuth.getUser(userId);
res.writeHead(200, { "Content-Type": "application/json" });
res.end(JSON.stringify({ message: "Authenticated", user: userData }));
} catch (error) {
res.writeHead(401, { "Content-Type": "application/json" });
res.end(JSON.stringify({ error: "Authentication failed" }));
}
} else {
res.writeHead(404);
res.end();
}
});
server.listen(3000);
```
### User Profile Retrieval
Fetch user details using the `getUser` method:
```typescript theme={null}
const user = await crossmintAuth.getUser(userId);
```
This provides access to user information such as email, phone number, and connected accounts (e.g., Twitter, Farcaster).
### JWT Verification
Verify JWTs independently using the `verifyCrossmintJwt` method:
```typescript theme={null}
const decodedJwt = crossmintAuth.verifyCrossmintJwt(token);
```
This is useful for validating tokens in middleware or specific endpoints. We expose our public keys for this purpose at [https://www.crossmint.com/.well-known/jwks.json](https://www.crossmint.com/.well-known/jwks.json).
# User Profile
Source: https://docs.crossmint.com/authentication/user-profile
Retrieve user profile data such as email or social login metadata
Crossmint Auth provides a way to retrieve and update user profile data, such as email, social login metadata, and more.
The user object contains the following information:
* `id`: User's unique identifier.
* `email`: User's email address.
* `phoneNumber`: User's phone number.
* `farcaster`: User's Farcaster account data.
* `twitter`: User's Twitter (X) account data.
Farcaster Account data contains:
* `fid`: User's Farcaster ID.
* `username`: User's Farcaster username.
* `bio`: User's Farcaster bio.
* `displayName`: User's Farcaster display name.
* `pfpUrl`: User's Farcaster profile image URL.
* `custody`: User's FID custody address.
* `verifications`: List of the user's verified addresses.
Twitter Account data contains:
* `id`: User's Twitter ID.
* `username`: User's Twitter username.
## Reading User Data
This API requires the `users.read` scope.
* **Server side**: Requires a server-side API key.
* **Client side**: Requires a client-side API key, and a logged in user.
### Server-side
For server-side operations, use the `@crossmint/server-sdk` that provides a `getUser` function to fetch user information.
```typescript theme={null}
import { createCrossmint, CrossmintAuth } from "@crossmint/server-sdk";
const crossmint = createCrossmint({ apiKey: "" });
const crossmintAuth = CrossmintAuth.from(crossmint);
async function getUser(userId: string) {
try {
const user = await crossmintAuth.getUser(userId);
console.log("User data:", user);
} catch (error) {
console.error("Error fetching user data:", error);
}
}
```
### Response Format
```json theme={null}
{
"id": "123",
"email": "test@test.com", // Optional (if user was created with email)
"phoneNumber": "123456789", // Optional (if user was created with phone number)
"farcaster": {
"fid": "123",
"username": "johndoe",
"bio": "Hello, I'm John Doe",
"displayName": "John Doe",
"pfpUrl": "https://example.com/pfp.jpg",
"custody": "0x1234567890123456789012345678901234567890",
"verifications": ["0x1234567890123456789012345678901234567890"]
},
"twitter": {
"id": "456",
"username": "johndoe"
}
}
```
### Client-side
Use the `useAuth` react hook from `@crossmint/client-sdk-react-ui` to access the user object.
If you would like to use a different framework, [contact support](https://help.crossmint.com/hc/en-us/requests/new)
```tsx React theme={null}
import { useAuth } from "@crossmint/client-sdk-react-ui";
function User() {
const { user } = useAuth();
if (!user) {
return
);
}
```
# User Webhooks
Source: https://docs.crossmint.com/authentication/webhooks
Crossmint provides webhooks to notify your application about important user-related events. Below are the details of the available webhooks
### Webhook Events
#### `users.created`
This webhook is triggered when a new user is created.
##### Payload
```json theme={null}
{
"type": "users.created",
"status": "success",
"data": {
"id": "123", // User's identifier
"email": "test@test.com", // Optional (if user was created with email)
"phoneNumber": "123456789" // Optional (if user was created with phone number)
}
}
```
#### `users.updated`
This webhook is triggered when a user's email or phone number is successfully updated.
##### Payload
```json theme={null}
{
"type": "users.updated",
"status": "success",
"data": {
"actionId": "1234", // Update action identifier
"userId": "123", // User identifier
"oldEmail": "test@test.com", // Optional (if user was created with email)
"newEmail": "test2@test2.com", // Optional (if user was created with email)
"oldPhoneNumber": "123456789", // Optional (if user was created with phone number)
"newPhoneNumber": "987654321" // Optional (if user was created with phone number)
}
}
```
### Handling Webhooks
To handle webhooks, you need to set up an endpoint in your application that can receive HTTP POST requests from Crossmint. Here is an example of how you might handle a webhook in a Node.js application:
```javascript theme={null}
import express from "express";
const app = express();
app.use(express.json());
function handleUserCreated(data) {
console.log("User created:", data.id);
}
function handleUserUpdated(data) {
console.log("User updated:", data.userId);
}
app.post("/webhooks", (req, res) => {
const event = req.body;
switch (event.type) {
case "users.created":
handleUserCreated(event.data);
break;
case "users.updated":
handleUserUpdated(event.data);
break;
default:
console.log(`Unhandled event type: ${event.type}`);
}
res.sendStatus(200);
});
app.listen(3000, () => {
console.log("Webhook server listening on port 3000");
});
```
# Crossmint help
Source: https://docs.crossmint.com/cli/help
List all commands and provides the help documentation for a given command
Run the following command from your terminal to list all commands
````
crossmint help
```
## Options
```
$ crossmint --help # Display help for command, including the supported flags
$ crossmint -h # Display help for command, including the supported flags
```
## Example
```
Usage: crossmint [options] [command]
Crossmint CLI Tool
Options:
-V, --version output the version number
-h, --help display help for command
Commands:
keys API Keys management commands
login [options] Login to Crossmint
logout Logout from Crossmint
projects Project management commands
whoami Show current user and environment
help [command] display help for command
```
````
# Get started with the Crossmint CLI
Source: https://docs.crossmint.com/cli/install
Manage and configure your Crossmint projects directly from the command line
### Latest CLI version -
The Crossmint CLI is a command-line tool that allows you to interact with Crossmint directly from your terminal. It can be installed via Homebrew or any javascript package managers and works on macOS, Linux, and Windows.
### Installation
To download and install Crossmint CLI, run the following command from your terminal:
```bash pnpm theme={null}
pnpm add -g @crossmint/cli
```
```bash yarn theme={null}
yarn global add @crossmint/cli
```
```bash npm theme={null}
npm install -g @crossmint/cli
```
```bash brew theme={null}
brew tap crossmint/tap
brew install crossmint
```
### Verify installation
After installing, verify the CLI is available and check the installed version:
```bash theme={null}
crossmint --version
```
### Update
To update to the latest version, re-run the install command for your package manager, or use the following:
```bash pnpm theme={null}
pnpm add -g @crossmint/cli@latest
```
```bash yarn theme={null}
yarn global add @crossmint/cli@latest
```
```bash npm theme={null}
npm install -g @crossmint/cli@latest
```
```bash brew theme={null}
brew upgrade crossmint
```
## Command Reference
The Crossmint CLI provides the following commands:
| Command | Description |
| ------------------------------------------------------------------------------------ | ------------------------------------------------------------- |
| `crossmint login [options]` | Login to Crossmint |
| `crossmint logout` | Logout from Crossmint |
| `crossmint whoami` | Show current user and environment |
| `crossmint help` | List and explains all commands |
| `crossmint keys create` | Create a new API Key |
| `crossmint keys list [type]` | List all API Keys (optionally filter by type: server\|client) |
| `crossmint keys delete ` | Delete an API Key |
| `crossmint keys edit ` | Edit the scopes of an API Key |
| `crossmint projects create` | Create a new project |
| `crossmint projects select [project-id]` | Select a project |
| `crossmint projects list` | List all projects |
| `crossmint projects details` | Show project details |
Refer to the individual command documentation for more detailed information on each command.
# Crossmint keys create
Source: https://docs.crossmint.com/cli/keys/create
Create a new API key
The keys create command allows you to create a new API key for your Crossmint project.
To use it, run the following command from your terminal
```bash theme={null}
crossmint keys create
```
**Server-side keys** are view-once in Production, make sure to copy your API key secret when it’s displayed, as it
won’t be shown again.
**Client-side keys** require selecting the app type and adding allowed origins—use domain URLs for web or
package/bundle IDs for mobile.
## Example
Start the API key creation process:
```bash theme={null}
crossmint keys create
```
Select the type of key you want to create:
```
? Select Key Usage: (Use arrow keys)
Server side
> Client side
```
For client-side keys, select the application type:
```
? Select client platform: (Use arrow keys)
> Web
Mobile
```
Specify the allowed origins for your key.
* For web enter the domain, eg: [https://crossmint.com](https://crossmint.com)
* For mobile enter your Android Package name or IOS Bundle ID, eg: com.company.appname
Enter multiple domains or app identifiers separated by a comma.
```
? Enter whitelisted domain or localhost (e.g., https://www.yourdomain.com) (separate multiple domains with a comma):
https://www.crossmint.com
```
Select the scopes you want to add to the key by pressing space. Once you are ready, press enter to create the key:
```
? Select scopes for client key:
● Read wallet (wallets.read)
● Create wallet (wallets.create)
○ Create Transaction (wallets.transactions.create)
○ Sign Transaction (wallets.transactions.sign)
○ Read Transactions (wallets.transactions.read)
○ Create Wallet Signatures (wallets.signatures.create)
```
The CLI will display the created API key information, this includes the Key ID and Client Secret:
```
✅ API Key created successfully!
Key ID: 67ff9c388687868382...
🔒 Key Secret: ck_production_3218u5TFZMGkK16uLbKfxDsJ4xtwSd2xtZRa9A8cbe64oRMe1J148XTGbuSvqmxB4k9TspLUAbKyt2RTqbArGBdkvARxkCg8S1tgwb4ie3gUNFw74vTuCUhSpu7ojkjGxY6epj...
```
# Crossmint keys delete
Source: https://docs.crossmint.com/cli/keys/delete
Delete an API key
The keys delete command allows you to delete an existing API key from your Crossmint project.
To use it, run the following command from your terminal
```bash theme={null}
crossmint keys delete
```
Where `` is the ID of the API key you want to delete.
You can get the key id by running `keys list` command
## Example
Delete an API key:
```bash theme={null}
crossmint keys delete sk_test_abcd1234
```
You will be prompted to confirm the deletion:
```
? Are you sure you want to delete this API key? This action cannot be undone. (y/N)
```
After confirming, you'll see a success message:
```
✅ API Key deleted successfully!
```
**Note:** Once an API key is deleted, any applications or services using that key will no longer be able to authenticate with the Crossmint API.
# Crossmint keys edit
Source: https://docs.crossmint.com/cli/keys/edit
Edit an API key
The keys edit command allows you to modify the scopes of an existing server API key.
To use it, run the following command from your terminal
```bash theme={null}
crossmint keys edit
```
Where `` is the ID of the API key you want to edit. You can get the key id by running `keys list` command.
## Example
Edit an API key:
```bash theme={null}
crossmint keys edit sk_test_abcd1234
```
For client-side keys, edit the allowed origins for your key. Press `` for editing or `` for confirming the value.
* For web enter the domain, eg: [https://crossmint.com](https://crossmint.com)
* For mobile enter your Android Package name or IOS Bundle ID, eg: com.company.appname
Enter multiple domains or app identifiers separated by a comma.
```
? Enter whitelisted domain or localhost (e.g., https://www.yourdomain.com) (separate multiple domains with a comma):
Press to edit or to confirm current value (https://www.crossmint.com)
```
The CLI will display the current scopes and prompt you to select new ones:
```
Current scopes: Wallets
? Select the new scopes for this key: (Press to select, to toggle all, to invert selection, and to proceed)
❯◉ Wallets
◉ Minting
◯ Payments
◯ Authentication
```
After selecting the new scopes, you'll see a confirmation message:
```
✅ API Key updated successfully!
```
# Crossmint keys list
Source: https://docs.crossmint.com/cli/keys/list
List all API keys
The keys list command displays all API keys associated with your Crossmint project.
To use it, run the following command from your terminal
```bash theme={null}
crossmint keys list [type]
```
Where `[type]` is an optional parameter to filter keys by type (server or client).
## Example
List all API keys:
```bash theme={null}
crossmint keys list
```
```
=========
1. Key ID: 67d444369323ea298a4ef9f8
Key Secret: ******************4jTa
Type Server
Created By guille.a@paella.dev
Created 3/14/2025, 3:59:02 PM
Scopes wallets.read, wallets.create, wallets:transactions.create, wallets:transactions.sign, wallets:transactions.read, wallets:signatures.create, wallets:signatures.read, wallets:nfts.read, wallets:balance.read, wallets.fund, wallets:messages.sign
=========
2. Key ID: 679217c403723242bd87d3e75
Key Secret: ck_production_AGUysPdnSg3rW2opefTqWAbfXRaCLtEBiYCSNL7
Jbm9L
Type Client
Created 1/23/2025, 11:19:48 AM
Scopes wallets.read, wallets.create, wallets:nfts.read, wallets:transactions.create
Whitelisted App IDs com.company.appname
=========
```
List only server API keys:
```bash theme={null}
crossmint keys list server
```
```
=========
1. Key ID: 67d444369bbbea298a4ef932
Key Secret: ******************4jTa
Type Server
Created By guille.a@paella.dev
Created 3/14/2025, 3:59:02 PM
Scopes wallets.read, wallets.create, wallets:transactions.create, wallets:transactions.sign, wallets:transactions.read, wallets:signatures.create, wallets:signatures.read, wallets:nfts.read, wallets:balance.read, wallets.fund, wallets:messages.sign
=========
```
# Crossmint login
Source: https://docs.crossmint.com/cli/login
Authenticate to the Crossmint platform
The login command authenticates you with the Crossmint platform. It allows you to access your account and manage API keys and projects through the CLI.
To use it, run the following command from your terminal
```bash theme={null}
crossmint login [options]
```
## Options
| Option | Description |
| ------------------------- | --------------------------------------------------- |
| `-e, --env ` | Environment to login to (`staging` or `production`) |
## Example
Start the login process:
```bash theme={null}
crossmint login
```
Or specify the environment directly to skip the prompt:
```bash theme={null}
crossmint login --env production
```
Select an environment:
```
? Select environment: (Use arrow keys)
> Production
Staging
```
The CLI will open a browser window to authorize your device. If you're not signed in to Crossmint in the browser, you'll need to login first. Once the device is authorized, you can close the browser and return to the terminal.
```
✅ Login successful! You are now authenticated in the Production environment.
```
You'll then be prompted to select a project:
```
? Select a project: (Use arrow keys)
> My Project
Website Integration
Mobile App
```
# Crossmint logout
Source: https://docs.crossmint.com/cli/logout
Sign out from the Crossmint platform
The logout command signs you out from the Crossmint platform by removing your session token from local storage.
To use it, run the following command from your terminal
```bash theme={null}
crossmint logout
```
## Example
Sign out from Crossmint:
```bash theme={null}
crossmint logout
```
After running the command, you'll see a confirmation message:
```
✅ Logout successful! You are no longer authenticated.
```
After logging out, you will need to use the `login` command to authenticate again before using other commands that require authentication.
# Crossmint projects create
Source: https://docs.crossmint.com/cli/projects/create
Create a new project in Crossmint
The projects create command allows you to create a new project in your Crossmint account.
To use it, run the following command from your terminal
```bash theme={null}
crossmint projects create [options]
```
## Options
| Option | Description |
| ------------------- | ------------------------ |
| `-n, --name ` | Give your project a name |
## Example
Create a new project:
```bash theme={null}
crossmint projects create
```
You'll be prompted to enter a name for the project:
```
? Enter project name: My New Project
```
After entering a name, you'll see a confirmation message:
```
✔ ✅ Project created successfully!
📌 Project Details:
Name: New Project
ID: 4b0392a3-35c3-458c-a43e-32d2cdc65eda
```
You'll be ask to confirm if you want to select the created project for subsequent commands.
````
? Do you want to select this project? (Y/n)
```
You can also specify the project name directly using the `--name` option:
```bash
crossmint projects create --name "My New Project"
```
````
# Crossmint projects details
Source: https://docs.crossmint.com/cli/projects/details
View details of a project
The projects details command displays information about the currently selected project.
To use it, run the following command from your terminal
```bash theme={null}
crossmint projects details
```
## Example
View details of the current project:
```bash theme={null}
crossmint projects details
```
You'll see the information about your project:
```
📌 Project Details:
Name: New Project
ID: f5e37bee-1a81-4df1-8081-7670eee2a629
Wallet Type: Custodial
```
This command is useful for verifying which project you're currently working with and viewing its basic information.
# Crossmint projects list
Source: https://docs.crossmint.com/cli/projects/list
List all projects in your Crossmint account
The projects list command displays all projects associated with your Crossmint account.
To use it, run the following command from your terminal
```bash theme={null}
crossmint projects list
```
## Example
List all your projects:
```bash theme={null}
crossmint projects list
```
You'll see a list of all your projects with their IDs:
```
✔ Found 3 projects:
1. My New Project
ID: 4b0392a3-35c3-458c-a43e-32d2cdc65eda
2. Website Integration
ID: f5e37bee-1a81-4df1-8081-7670eee2a629
3. Mobile App
ID: 9a1b2c3d-4e5f-6789-abcd-ef0123456789
```
This command is useful for viewing all available projects and retrieving project IDs, which can then be passed to `crossmint projects select` for non-interactive project selection.
# Crossmint projects select
Source: https://docs.crossmint.com/cli/projects/select
Select a project to work with
The projects select command allows you to choose which Crossmint project you want to work with for subsequent commands.
To use it, run the following command from your terminal
```bash theme={null}
crossmint projects select [project-id]
```
## Arguments
| Argument | Description |
| -------------- | ------------------------------------------------------------------- |
| `[project-id]` | Optional. The ID of the project to select (for non-interactive use) |
## Example
### Interactive selection
Select a project interactively:
```bash theme={null}
crossmint projects select
```
You'll be presented with a list of your projects:
```
? Select a project: (Use arrow keys)
> My New Project
Website Integration
Mobile App
Test Project
```
After selecting a project, you'll see a confirmation message:
```
✅ Project My New Project selected
```
### Non-interactive selection
You can also pass a project ID directly to skip the interactive prompt. This is useful for scripting and agent-based workflows:
```bash theme={null}
crossmint projects select 4b0392a3-35c3-458c-a43e-32d2cdc65eda
```
```
✅ Project My New Project selected
```
Use `crossmint projects list` to view all available projects and their IDs.
After selecting a project, commands that require a project context (like API key management) will use the selected project.
# Crossmint whoami
Source: https://docs.crossmint.com/cli/whoami
Display current user, environment and selected project
The whoami command displays information about the currently authenticated user, the environment you're working in and the selected project.
To use it, run the following command from your terminal
```bash theme={null}
crossmint whoami
```
## Example
Check your current authentication status:
```bash theme={null}
crossmint whoami
```
You'll see output showing your email and environment:
```
You are logged in to 🍀Crossmint as: user@example.com - Environment: Production - Selected Project: My New Project
```
This command is useful for verifying your authentication status, checking which environment and project you're working in.
# Data Requirements
Source: https://docs.crossmint.com/identity/data-requirements
Required data fields by activity type and region
The data required for each user depends on the type of regulated activity they want to perform and the user's residency.
## Requirements for non-US residents
The following tables show what information is required for each activity type for users residing outside the United States. To see the specific fields required for each object follow the [Create User API](/api-reference/users/upsert-user) documentation.
### Data Requirements
| Object | Payouts | On/offramp |
| ------------------- | :-----: | :--------: |
| userDetails | ✅ | ✅ |
| kycData | | ✅ |
| dueDiligence | | ✅ |
| verificationHistory | | ✅ |
### Document Requirements
Specific documents may be required depending on the activity. Use the [Upload Document API](/api-reference/users/upload-document) to upload the necessary materials.
| Document Type | Payouts | On/offramp | On/offramp with EDD\* |
| ----------------- | :-----: | :--------: | :-------------------: |
| Identity document | | ✅ | ✅ |
| Selfie | | ✅ | ✅ |
| Proof of address | | | ✅ |
| Proof of income | | | ✅ |
\*Enhanced Due Diligence (EDD) is required for high-risk individuals or regions.
**Identity document types for non-US residents:**
* For residents of countries outside of EU/EEA, only passport (`id-passport`) is accepted. Driving licenses or local ID cards are not accepted.
* For residents of EU/EEA countries, passport (`id-passport`), ID cards (`id-idcard-front`, `id-idcard-back`), or residency permit (`id-residency-permit`, Spain residents only) are accepted.
## Requirements for US residents
The same data and document requirements apply for users residing in the United States, with the following exceptions:
* The `kycData.phoneNumber` field is **required** for US residents (it is optional for non-US residents).
* The `dueDiligence` object is **not required** for US residents in most states. However, it is **required** for residents of the following states: **New York (NY)**, **North Carolina (NC)**, **Tennessee (TN)**, **Texas (TX)**, and **Virginia (VA)**.
* When you include the `dueDiligence` object for a US resident, its `estimatedYearlyIncome` field is **required** (it is optional for non-US residents). For standard offramp in states that do not require due diligence, you can omit the `dueDiligence` object entirely.
* US residents provide their identity via SSN in the `kycData.identityDocument` field (type `"ssn"`). No identity document upload or selfie is required for standard on/offramp. However, if Enhanced Due Diligence (EDD) is triggered, US users must also upload a passport (`id-passport`) or driver's license (`id-idcard-front`, `id-idcard-back`).
* Proof of income is **not required** for US residents under EDD.
## Next Steps
Register your first user and upload documents in minutes.
Contact the Crossmint sales team for support.
# Overview
Source: https://docs.crossmint.com/identity/overview
Register user data to enable access to regulated Crossmint products.
Crossmint's regulated products for stablecoin orchestration, including [onramp](/onramp/overview), offramp, and [payouts](/stablecoin-orchestration/regulated-transfers/overview)—require user data registration to comply with financial regulations. Compliance checks are performed by Crossmint when a registered user attempts a regulated action.
## How it works
* **Run initial verification**: Your company runs or identifies existing KYC, ID verification, and liveness checks with your own IDV provider (such as Persona or Sumsub)
* **Pass KYC data to Crossmint**: Share that KYC data with Crossmint via API so that regulated actions (onramp, offramp, payouts) can leverage that existing KYC data
* **Crossmint handles additional screening**: Crossmint runs sanction and PEP (Politically Exposed Persons) screens, and performs ongoing monitoring to ensure compliance
If your company has not provided all required information, users will be prompted to provide it client-side when initiating a regulated activity, as it's done with [Crossmint Onramp](/onramp/overview).
## Data Requirements Vary
The information required for each user depends on the country and the activity type:
* **Payouts**: Basic user details for payouts
* **Onramp/Offramp**: Additional user information
See the [Data Requirements](/identity/data-requirements) page for a detailed breakdown of required fields.
## Getting Started
Register your first user and upload documents in minutes.
See what data is required for each activity and region.
## FAQs
Crossmint supports Persona and Sumsub as identity verification (IDV) vendors. To use any other vendor, you must get it approved first—raise it with your Customer Success Engineer (CSE).
Token sharing is not yet supported. If this is something you need, reach out to your Customer Success Engineer (CSE).
A hybrid approach is not yet supported. You either run your own identity verification and pass that data to Crossmint, or use Crossmint's embedded KYC in the checkout.
# Quickstart ⚡
Source: https://docs.crossmint.com/identity/quickstart
Register users and upload documents to enable regulated product access.
This quickstart guide walks you through registering a user, uploading documents, and fetching document details using Crossmint's APIs. These steps enable your users to access regulated products like onramp, offramp, and payouts.
User locators use the `userId:` format, a unique identifier you choose for the user, for example `userId:johnd-123`.
Before sharing any user KYC data with Crossmint, you must ensure your users have accepted Crossmint's Privacy Policy. This is a prerequisite for companies sharing their customer's KYC data with Crossmint.
Consent must be collected in your app through a compliant flow (explicit checkbox, exact wording, visible Crossmint logo, re-consent on policy updates). See [Register User Data](/identity/register-user-data) for the full consent-collection requirements.
Meeting these requirements is mandatory for sharing user KYC data with Crossmint. Only record acceptance through the API below after the user has granted consent this way. Crossmint audits data-sharing integrations for compliance with these requirements.
Once the user has accepted the privacy policy, record their acceptance using the following API call:
```bash cURL theme={null}
curl --request PUT \
--url 'https://staging.crossmint.com/api/2025-06-09/users/userId:johnd-123/legal-documents' \
--header 'X-API-KEY: ' \
--header 'Content-Type: application/json' \
--data '{
"type": "crossmint-privacy-policy",
"acceptedAt": "2025-10-05T14:48:00Z"
}'
```
```javascript Node.js theme={null}
const userLocator = "userId:johnd-123";
const options = {
method: 'PUT',
headers: {'X-API-KEY': '', 'Content-Type': 'application/json'},
body: JSON.stringify({
type: "crossmint-privacy-policy",
acceptedAt: "2025-10-05T14:48:00Z" // ISO 8601 date string
})
};
fetch(`https://staging.crossmint.com/api/2025-06-09/users/${userLocator}/legal-documents`, options)
.then(response => response.json())
.then(response => console.log(response))
.catch(err => console.error(err));
```
```python Python theme={null}
import requests
user_locator = "userId:johnd-123"
url = f"https://staging.crossmint.com/api/2025-06-09/users/{user_locator}/legal-documents"
payload = {
"type": "crossmint-privacy-policy",
"acceptedAt": "2025-10-05T14:48:00Z" # ISO 8601 date string
}
headers = {
"X-API-KEY": "",
"Content-Type": "application/json"
}
response = requests.put(url, json=payload, headers=headers)
print(response.json())
```
This call also creates a user with the specified `userLocator` if one does not already exist.
Register a user with Crossmint by specifying their `userLocator`, their personal details, and KYC data using the [Create User](/api-reference/users/upsert-user) endpoint
```bash cURL theme={null}
curl --request PUT \
--url 'https://staging.crossmint.com/api/2025-06-09/users/userId:johnd-123' \
--header 'X-API-KEY: ' \
--header 'Content-Type: application/json' \
--data '{
"userDetails": {
"firstName": "John",
"lastName": "Doe",
"dateOfBirth": "1995-01-01",
"countryOfResidence": "DE"
}
}'
```
```javascript Node.js theme={null}
const userLocator = "userId:johnd-123";
const options = {
method: 'PUT',
headers: {'X-API-KEY': '', 'Content-Type': 'application/json'},
body: JSON.stringify({
userDetails: {
firstName: "John",
lastName: "Doe",
dateOfBirth: "1995-01-01",
countryOfResidence: "DE"
}
})
};
fetch(`https://staging.crossmint.com/api/2025-06-09/users/${userLocator}`, options)
.then(response => response.json())
.then(response => console.log(response))
.catch(err => console.error(err));
```
```python Python theme={null}
import requests
user_locator = "userId:johnd-123"
url = f"https://staging.crossmint.com/api/2025-06-09/users/{user_locator}"
payload = {
"userDetails": {
"firstName": "John",
"lastName": "Doe",
"dateOfBirth": "1995-01-01",
"countryOfResidence": "DE"
}
}
headers = {
"X-API-KEY": "",
"Content-Type": "application/json"
}
response = requests.put(url, json=payload, headers=headers)
print(response.json())
```
Once you have registered relevant user information you can trigger checks via the [Trigger Identity Verification](/api-reference/users/trigger-identity-verification) endpoint.
```bash cURL theme={null}
curl --request PUT \
--url 'https://staging.crossmint.com/api/2025-06-09/users/userId:johnd-123/identity-verification' \
--header 'X-API-KEY: '
```
```javascript Node.js theme={null}
const userLocator = "userId:johnd-123";
const options = {
method: 'PUT',
headers: {
'X-API-KEY': ''
}
};
fetch(`https://staging.crossmint.com/api/2025-06-09/users/${userLocator}/identity-verification`, options)
.then(response => response.json())
.then(response => console.log(response))
.catch(err => console.error(err));
```
```python Python theme={null}
import requests
user_locator = "userId:johnd-123"
url = f"https://staging.crossmint.com/api/2025-06-09/users/{user_locator}/identity-verification"
headers = {
"X-API-KEY": ""
}
response = requests.put(url, headers=headers)
print(response.json())
```
The response will include the eligibility status for each verification type (`regulated-transfer`, `onramp`, `onramp-light`, `offramp`, `offramp-light`):
* `not-started`: Verification has not been initiated
* `pending-privacy-policy`: User has not yet accepted Crossmint's Privacy Policy
* `requires-data`: Additional user data or documents are needed
* `pending-review`: Verification is in progress
* `pending-manual-review`: Verification requires manual review by Crossmint's compliance team
* `verified`: User has passed verification
* `rejected`: User has failed verification
Once a product reads `verified`, the user can use it. A `verified` `offramp` status, for example, means the user can cash out through [Offramp](/offramp/overview).
If any of them return `requires-data`, you must aggregate the `missingData` and `missingDocuments` arrays from those specific eligibility objects. Then, update the user's information using the [Update User](/api-reference/users/upsert-user) endpoint and/or upload additional documents using the [Upload Document](/api-reference/users/upload-document) endpoint, and finally re-trigger the verification.
```json theme={null}
{
"eligibility": [
{
"type": "regulated-transfer",
"status": "pending-review" // or "verified"
},
{
"type": "onramp",
"status": "requires-data",
"missingData": ["kyc-data", "due-diligence", "verification-history"],
"missingDocuments": ["id", "selfie-front"] // "proof-of-address" and "proof-of-income" may also be requested in case of Enhanced Due Diligence
},
{
"type": "onramp-light",
"status": "requires-data",
"missingData": ["kyc-data"] // light KYC requires less data and no document uploads
},
{
"type": "offramp",
"status": "requires-data",
"missingData": ["kyc-data", "due-diligence", "verification-history"],
"missingDocuments": ["id", "selfie-front"] // "proof-of-address" and "proof-of-income" may also be requested in case of Enhanced Due Diligence
},
{
"type": "offramp-light",
"status": "requires-data",
"missingData": ["kyc-data"] // light KYC requires less data and no document uploads
}
]
}
```
Register additional user information with Crossmint so the user can access onramp and offramp services.
Calling this endpoint again with the same `userLocator` will update the existing user's information. The endpoint is not additive so specify all relevant user information when updating the user.
```bash cURL theme={null}
curl --request PUT \
--url 'https://staging.crossmint.com/api/2025-06-09/users/userId:johnd-123' \
--header 'X-API-KEY: ' \
--header 'Content-Type: application/json' \
--data '{
"userDetails": {
"firstName": "John",
"lastName": "Doe",
"dateOfBirth": "1995-01-01",
"countryOfResidence": "DE"
},
"kycData": {
"nationality": "DE",
"addressOfResidence": {
"line1": "123 Hauptstrasse",
"line2": "Apt 5",
"city": "Berlin",
"stateOrRegion": "Berlin",
"postalCode": "10115"
},
"email": "johnd@example.com",
"identityDocument": {
"type": "passport",
"number": "AS12321",
"issuingCountryCode": "DE"
},
"ipAddresses": ["203.0.113.1"]
},
"dueDiligence": {
"employmentStatus": "full-time",
"sourceOfFunds": "salary-disbursement",
"industry": "financial-institution",
"expectedYearlyTxVolume": "volume-0-25k"
},
"verificationHistory": {
"idVerificationTimestamp": "2024-01-15T10:30:00Z",
"livenessVerificationTimestamp": "2024-01-15T10:32:00Z"
}
}'
```
```javascript Node.js theme={null}
const userLocator = "userId:johnd-123";
const options = {
method: 'PUT',
headers: {'X-API-KEY': '', 'Content-Type': 'application/json'},
body: JSON.stringify({
userDetails: {
firstName: "John",
lastName: "Doe",
dateOfBirth: "1995-01-01",
countryOfResidence: "DE"
},
kycData: {
nationality: "DE",
addressOfResidence: {
line1: "123 Hauptstrasse",
line2: "Apt 5",
city: "Berlin",
stateOrRegion: "Berlin",
postalCode: "10115"
},
email: "johnd@example.com",
identityDocument: {
type: "passport",
number: "AS12321",
issuingCountryCode: "DE"
},
ipAddresses: ["203.0.113.1"]
},
dueDiligence: {
employmentStatus: "full-time",
sourceOfFunds: "salary-disbursement",
industry: "financial-institution",
expectedYearlyTxVolume: "volume-0-25k"
},
verificationHistory: {
idVerificationTimestamp: "2024-01-15T10:30:00Z",
livenessVerificationTimestamp: "2024-01-15T10:32:00Z"
}
})
};
fetch(`https://staging.crossmint.com/api/2025-06-09/users/${userLocator}`, options)
.then(response => response.json())
.then(response => console.log(response))
.catch(err => console.error(err));
```
```python Python theme={null}
import requests
user_locator = "userId:johnd-123"
url = f"https://staging.crossmint.com/api/2025-06-09/users/{user_locator}"
payload = {
"userDetails": {
"firstName": "John",
"lastName": "Doe",
"dateOfBirth": "1995-01-01",
"countryOfResidence": "DE"
},
"kycData": {
"nationality": "DE",
"addressOfResidence": {
"line1": "123 Hauptstrasse",
"line2": "Apt 5",
"city": "Berlin",
"stateOrRegion": "Berlin",
"postalCode": "10115"
},
"email": "johnd@example.com",
"identityDocument": {
"type": "passport",
"number": "AS12321",
"issuingCountryCode": "DE"
},
"ipAddresses": ["203.0.113.1"]
},
"dueDiligence": {
"employmentStatus": "full-time",
"sourceOfFunds": "salary-disbursement",
"industry": "financial-institution",
"expectedYearlyTxVolume": "volume-0-25k"
},
"verificationHistory": {
"idVerificationTimestamp": "2024-01-15T10:30:00Z",
"livenessVerificationTimestamp": "2024-01-15T10:32:00Z"
}
}
headers = {
"X-API-KEY": "",
"Content-Type": "application/json"
}
response = requests.put(url, json=payload, headers=headers)
print(response.json())
```
Upload identity or supporting documents for the user using the [Upload Document](/api-reference/users/upload-document) endpoint. Documents are associated with the user via their `userLocator`.
```bash cURL theme={null}
curl --request POST \
--url 'https://staging.crossmint.com/api/2025-06-09/documents' \
--header 'X-API-KEY: ' \
--header 'Content-Type: application/json' \
--data '{
"reference": {
"userLocator": "userId:johnd-123"
},
"documentType": "id-passport",
"data": "",
"expiresAt": "2030-12-31"
}'
```
```javascript Node.js theme={null}
const options = {
method: 'POST',
headers: {'X-API-KEY': '', 'Content-Type': 'application/json'},
body: JSON.stringify({
reference: {
userLocator: "userId:johnd-123"
},
documentType: "id-passport",
data: "",
expiresAt: "2030-12-31"
})
};
fetch('https://staging.crossmint.com/api/2025-06-09/documents', options)
.then(response => response.json())
.then(response => console.log(response))
.catch(err => console.error(err));
```
```python Python theme={null}
import requests
url = "https://staging.crossmint.com/api/2025-06-09/documents"
payload = {
"reference": {
"userLocator": "userId:johnd-123"
},
"documentType": "id-passport",
"data": "",
"expiresAt": "2030-12-31"
}
headers = {
"X-API-KEY": "",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.json())
```
**Supported document types:**
* Identity documents: `id-passport`, `id-idcard-front`, `id-idcard-back`, `id-residency-permit` (Spain residents only)
* Supporting documents: `proof-of-address`, `proof-of-income`
* Selfie documents: `selfie-front`, `selfie-left`, `selfie-right`
US residents must include `kycData.phoneNumber`, and provide their identity via SSN in `kycData.identityDocument`. No identity document upload or selfie is needed for standard on/offramp. However, if Enhanced Due Diligence (EDD) is triggered, US users must also upload a passport (`id-passport`) or driver's license (`id-idcard-front`, `id-idcard-back`). Proof of income is **not** required for US EDD users.Calling this endpoint again with the same `userLocator` and `documentType` will update the existing document's registered information.
Once you have registered the user's information and uploaded the required documents, trigger the KYC verification process using the [Trigger Identity Verification](/api-reference/users/trigger-identity-verification) endpoint.
```bash cURL theme={null}
curl --request PUT \
--url 'https://staging.crossmint.com/api/2025-06-09/users/userId:johnd-123/identity-verification' \
--header 'X-API-KEY: '
```
```javascript Node.js theme={null}
const userLocator = "userId:johnd-123";
const options = {
method: 'PUT',
headers: {
'X-API-KEY': ''
}
};
fetch(`https://staging.crossmint.com/api/2025-06-09/users/${userLocator}/identity-verification`, options)
.then(response => response.json())
.then(response => console.log(response))
.catch(err => console.error(err));
```
```python Python theme={null}
import requests
user_locator = "userId:johnd-123"
url = f"https://staging.crossmint.com/api/2025-06-09/users/{user_locator}/identity-verification"
headers = {
"X-API-KEY": ""
}
response = requests.put(url, headers=headers)
print(response.json())
```
The response will include the eligibility status for each verification type (`regulated-transfer`, `onramp`, `onramp-light`, `offramp`, `offramp-light`):
```json theme={null}
{
"eligibility": [
{
"type": "regulated-transfer",
"status": "verified"
},
{
"type": "onramp",
"status": "pending-review"
},
{
"type": "onramp-light",
"status": "pending-review"
},
{
"type": "offramp",
"status": "pending-review"
},
{
"type": "offramp-light",
"status": "pending-review"
}
]
}
```
Verification runs asynchronously. After triggering it, poll the [Get Identity Verification Status](/api-reference/users/get-identity-verification) endpoint until the relevant product status reads `verified`. It usually completes within a minute, and the transient state may be `not-started` or `pending-review`.
```bash cURL theme={null}
curl --request GET \
--url 'https://staging.crossmint.com/api/2025-06-09/users/userId:johnd-123/identity-verification' \
--header 'X-API-KEY: '
```
```javascript Node.js theme={null}
const userLocator = "userId:johnd-123";
const options = {
method: 'GET',
headers: {
'X-API-KEY': ''
}
};
fetch(`https://staging.crossmint.com/api/2025-06-09/users/${userLocator}/identity-verification`, options)
.then(response => response.json())
.then(response => console.log(response))
.catch(err => console.error(err));
```
```python Python theme={null}
import requests
user_locator = "userId:johnd-123"
url = f"https://staging.crossmint.com/api/2025-06-09/users/{user_locator}/identity-verification"
headers = {
"X-API-KEY": ""
}
response = requests.get(url, headers=headers)
print(response.json())
```
You can fetch a user's information at any time to see their current status, including when they last accepted valid legal documents. Use the Get User endpoint to retrieve this information:
```bash cURL theme={null}
curl --request GET \
--url 'https://staging.crossmint.com/api/2025-06-09/users/userId:johnd-123' \
--header 'X-API-KEY: '
```
```javascript Node.js theme={null}
const userLocator = "userId:johnd-123";
const options = {
method: 'GET',
headers: {
'X-API-KEY': ''
}
};
fetch(`https://staging.crossmint.com/api/2025-06-09/users/${userLocator}`, options)
.then(response => response.json())
.then(response => console.log(response))
.catch(err => console.error(err));
```
```python Python theme={null}
import requests
user_locator = "userId:johnd-123"
url = f"https://staging.crossmint.com/api/2025-06-09/users/{user_locator}"
headers = {
"X-API-KEY": ""
}
response = requests.get(url, headers=headers)
print(response.json())
```
The response includes the `legalDocuments` array, which shows the status of legal document acceptances:
```json theme={null}
{
"email": "john.doe@example.com",
"phoneNumber": "+1234567890",
"userId": "usr_1234567890",
"userDetails": true,
"kycData": false,
"dueDiligence": false,
"verificationHistory": false,
"legalDocuments": [
{
"type": "crossmint-privacy-policy",
"acceptedAt": "2024-01-15T10:30:00Z",
"validVersion": true
}
]
}
```
The `validVersion` field indicates whether the user has accepted the current version of the legal document. If `validVersion` is `false`, you should prompt the user to accept the updated terms.
## Launching in Production
For production, the steps are almost identical, but some changes are required:
1. Create a developer account on the [production console](https://www.crossmint.com/console)
2. Create a production server API key on the [API Keys](https://www.crossmint.com/console/projects/apiKeys) page with the API scopes `users.create`, `users.read`
3. Replace your test API key with the production key
4. Replace `staging.crossmint.com` with `www.crossmint.com` in the API URLs
## Learn More
See what data is required for each activity and region.
Contact the Crossmint sales team for support.
# Register User Data
Source: https://docs.crossmint.com/identity/register-user-data
Provide personal information about recipients in order to comply with regulation
Register the transfer recipient as a user in your Crossmint project, then attach the recipient data required for payouts.
Crossmint uses this data to screen recipients against Office of Foreign Assets Control (OFAC) sanctions lists.
## Requirements
* An API key
* A stable user identifier in your system (used as `userLocator`)
* A Crossmint wallet address for the recipient (or the ability to create one)
## Step 1: Accept Crossmint's Privacy Policy
Before sharing any user KYC data with Crossmint, you must ensure your users have accepted Crossmint's Privacy Policy. This is a prerequisite for any company sharing their customers' KYC data with Crossmint.
**Requirements for collecting consent**
Consent must be obtained following the guidelines below:
1. The user must have the ability to see the terms they must accept
2. The user must grant consent with a checkbox, unchecked by default
3. The user does not need to open the hyperlink to the privacy policy to view the terms
4. The user needs to see Crossmint's logo in the flow
5. The specific text that you must present to your end user is: **"I consent to my KYC data being processed by Crossmint in accordance with its Privacy Policy"**
6. The text can be localized but must link to the same privacy policy link provided above
7. If the privacy policy is updated, you must ask the user to agree to the above terms again
Meeting these requirements is mandatory for sharing user KYC data with Crossmint. Only record acceptance through the API below after the user has granted consent this way. Crossmint audits data-sharing integrations for compliance with these requirements.
Once the user has accepted the privacy policy, record their acceptance using the [Accept Legal Document](/api-reference/users/accept-legal-document) endpoint:
```bash cURL theme={null}
curl --request PUT \
--url 'https://staging.crossmint.com/api/2025-06-09/users/userId:johnd-123/legal-documents' \
--header 'X-API-KEY: ' \
--header 'Content-Type: application/json' \
--data '{
"type": "crossmint-privacy-policy",
"acceptedAt": "2025-10-05T14:48:00Z"
}'
```
```javascript Node.js theme={null}
const userLocator = "userId:johnd-123";
const options = {
method: 'PUT',
headers: {'X-API-KEY': '', 'Content-Type': 'application/json'},
body: JSON.stringify({
type: "crossmint-privacy-policy",
acceptedAt: "2025-10-05T14:48:00Z" // ISO 8601 date string
})
};
fetch(`https://staging.crossmint.com/api/2025-06-09/users/${userLocator}/legal-documents`, options)
.then(response => response.json())
.then(response => console.log(response))
.catch(err => console.error(err));
```
```python Python theme={null}
import requests
user_locator = "userId:johnd-123"
url = f"https://staging.crossmint.com/api/2025-06-09/users/{user_locator}/legal-documents"
payload = {
"type": "crossmint-privacy-policy",
"acceptedAt": "2025-10-05T14:48:00Z" # ISO 8601 date string
}
headers = {
"X-API-KEY": "",
"Content-Type": "application/json"
}
response = requests.put(url, json=payload, headers=headers)
print(response.json())
```
This call also creates a user with the specified `userLocator` if one does not already exist.
## Step 2: Create a User
Register the transfer recipient as a user in your Crossmint project and associate the user with a Crossmint wallet address.
There are two ways to create a user:
### Choose a Method
* Use Method 1 if you create the wallet and user in one API call
* Use Method 2 if you register the user first and create the wallet later
Create a wallet for a user and associate it to a `userLocator` by setting the `owner` property. This method creates in a
single API call both the wallet, and the user associated with it.
The `owner` property accepts a `userLocator`. This associates the wallet with the user identifier in your system.
```javascript theme={null}
const userLocator = "userId:johnd-123";
const options = {
method: 'POST',
headers: {'X-API-KEY': '', 'Content-Type': 'application/json'},
body: JSON.stringify({
chainType: "evm",
config: {
adminSigner: {
type: "email",
email: "johnd@example.com"
}
},
owner: userLocator
})
};
fetch('https://staging.crossmint.com/api/2025-06-09/wallets', options)
.then(response => response.json())
.then(response => console.log(response))
.catch(err => console.error(err));
```
```javascript theme={null}
import { useWallet } from '@crossmint/client-sdk-react-ui';
const { getOrCreateWallet } = useWallet();
// The "owner" property is inferred by the JWT token, no need to specify it
const wallet = await getOrCreateWallet({
chain: "evm",
signer: {
type: "email",
email: "johnd@example.com"
}
});
```
Register a user with Crossmint without creating a wallet yet. Later, when you create a wallet for that registered user,
you can link the wallet's `owner` property to that user's registered `userLocator` value. This will associate the wallet with the user identifier in your system.
**Step 1: Register the user**
```javascript theme={null}
const userLocator = "userId:johnd-123";
const options = {
method: 'PUT',
headers: {'X-API-KEY': '', 'Content-Type': 'application/json'}
};
fetch(`https://staging.crossmint.com/api/2025-06-09/users/${userLocator}`, options)
.then(response => response.json())
.then(response => console.log(response))
.catch(err => console.error(err));
```
**Step 2: Create wallet and link to registered user**
When you are ready to create a wallet for the registered user, use the same `userLocator` in the `owner` field:
```javascript theme={null}
const userLocator = "userId:johnd-123";
const options = {
method: 'POST',
headers: {'X-API-KEY': '', 'Content-Type': 'application/json'},
body: JSON.stringify({
chainType: "evm",
config: {
adminSigner: {
type: "email",
email: "johnd@example.com"
}
},
owner: userLocator
})
};
fetch('https://staging.crossmint.com/api/2025-06-09/wallets', options)
.then(response => response.json())
.then(response => console.log(response))
.catch(err => console.error(err));
```
```javascript theme={null}
import { useWallet } from '@crossmint/client-sdk-react-ui';
const { getOrCreateWallet } = useWallet();
// The "owner" property is inferred by the JWT token, no need to specify it
const wallet = await getOrCreateWallet({
chain: "evm",
signer: {
type: "email",
email: "johnd@example.com"
}
});
```
## Step 3: Register User Data
Use the [Create User API](/api-reference/users/upsert-user) to attach `userDetails` to the user.
To see data requirements by activity and region, see [Data Requirements](/identity/data-requirements).
For payouts, the only information needed is `userDetails`.
```javascript REST theme={null}
const userLocator = "userId:johnd-123";
const options = {
method: 'PUT',
headers: {'X-API-KEY': '', 'Content-Type': 'application/json'},
body: JSON.stringify({
userDetails: {
firstName: "John",
lastName: "Doe",
dateOfBirth: "2007-01-01",
countryOfResidence: "DE"
}
})
};
fetch(`https://staging.crossmint.com/api/2025-06-09/users/${userLocator}/`, options)
.then(response => response.json())
.then(response => console.log(response))
.catch(err => console.error(err));
```
## Guides
# Using AI Assistants
Source: https://docs.crossmint.com/introduction/ai-assistants
Integrate faster with Crossmint by ingesting the docs into your AI-based code helpers (like Cursor, GitHub Copilot, etc.).
AI assistant ready docs
## Integrate with Cursor
1. Navigate to Cursor Settings > Features > Docs
2. Select "Add new doc" and paste the following URL:
```
https://docs.crossmint.com/llms-full.txt
```
3. Use `@docs -> Crossmint` to reference Crossmint's docs in your code.
***
> To find navigation and other pages in this documentation, fetch the llms.txt file at: [https://docs.crossmint.com/llms.txt](https://docs.crossmint.com/llms.txt)
## Crossmint Docs MCP Server
### Overview
Model Context Protocol (MCP) is a standard that enables AI agents to connect quickly and efficiently to external tools or data sources.
### Server Details
Instead of loading the entire documentation with every interaction, the server receives your query and returns only the most relevant sections.
* **Name:** Crossmint Docs
* **URL:** [https://docs.crossmint.com/mcp](https://docs.crossmint.com/mcp)
* **Transport:** Streamable HTTP
### Installation
Run the following command in your terminal:
```bash theme={null}
claude mcp add --transport http crossmint-docs https://docs.crossmint.com/mcp
```
Add it globally by opening the command palette (`Cmd/Ctrl + Shift + P`) and selecting `> Cursor Settings > Tools & MCP > Add Custom MCP`:
```json theme={null}
{
"mcpServers": {
"Crossmint Docs": {
"type": "http",
"url": "https://docs.crossmint.com/mcp"
}
}
}
```
Add it globally by opening the command palette (`Cmd/Ctrl + Shift + P`) and selecting `MCP: Open User Configuration`:
```json theme={null}
{
"servers": {
"Crossmint Docs": {
"type": "http",
"url": "https://docs.crossmint.com/mcp"
}
}
}
```
### Usage
Once you have configured and connected the MCP Server, your agent will have access to a single tool called `SearchCrossmintDocs`. This tool allows it to search through the Crossmint documentation to find relevant information, code examples, API references, and guides.
**Example queries (input):**
* "How to create non-custodial wallets for my users"
* "How to get a staging API key"
* "Does Crossmint support Solana?"
* "How to implement transfer webhooks?"
**Example output:**
A list of results that includes the following fields: Title, Link, and Content, along with code examples when available.
```json theme={null}
{
"results": [
{
"title": "Server Wallets Quickstart",
"link": "https://docs.crossmint.com/solutions/story-protocol/wallets/server-side-wallets",
"content": "In this quickstart, you will create new user wallets on Story...",
"codeExample": "const response = await fetch('https://staging.crossmint.com/api/2022-06-09/wallets', {\n method: 'POST',\n headers: {\n 'X-API-KEY': '',\n 'Content-Type': 'application/json'\n },\n body: JSON.stringify({\n type: '',\n linkedUser: 'email:user@example.com'\n })\n});"
},
{
"title": "Amazon Integration Steps",
"link": "https://docs.crossmint.com/solutions/ai-agents/agentic-commerce/inventory",
"content": "Create a project in the Crossmint Console...",
"codeExample": "const crossmintOrder = await fetch(`https://${baseUrl}.crossmint.com/api/2022-06-09/orders`, {\n method: 'POST',\n headers: {\n 'X-API-KEY': `${API_KEY}`,\n 'Content-Type': 'application/json'\n },\n body: JSON.stringify({\n recipient: {\n email: 'john@example.com'\n },\n lineItems: [{ productLocator: 'amazon:B00O79SKV6' }]\n })\n});"
}
]
}
```
# Getting Started
Source: https://docs.crossmint.com/introduction/getting-started
Before you can start integrating Crossmint into your application, you need to create a Crossmint account and set up a new project in the Staging Console. This guide will walk you through those steps.
Open Crossmint's [staging console](https://staging.crossmint.com/console) and create an account
**Ready for launch?**
When it's time to go live, simply create an account in the [production console](https://www.crossmint.com/console) and replicate the resources you need.
If you've just created an account for the first time, you'll be taken directly to project creation process. Add the name to your project and continue.
In the [overview of your project](https://staging.crossmint.com/console/overview) you will find the client and server API keys you need to integrate Crossmint into your application.
Now that your project is created in the Console, you can start integrating Crossmint products into your application.
Create wallets for users, agents, and companies.
Authenticate users with email, socials, or wallets.
Sell tokens and 1 billion physical products.
Fund wallets with stablecoins or crypto.
Mint, distribute, and manage tokens at scale.
Issue and verify W3C credentials.
# Glossary
Source: https://docs.crossmint.com/introduction/glossary
Key terms and definitions used across the Crossmint documentation.
A reference of key terms used throughout the Crossmint documentation. If you encounter an unfamiliar term in any guide or concept page, check here first.
| Term | Definition |
| --------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Smart contract wallet** | A wallet deployed as a smart contract on a public blockchain. It lives onchain — not inside Crossmint's servers — and its behavior is governed by auditable code. On EVM chains, Crossmint wallets implement [ERC-4337](https://eips.ethereum.org/EIPS/eip-4337) with [ERC-7579](https://eips.ethereum.org/EIPS/eip-7579) modular extensions. See [Architecture](/wallets/architecture). |
| **Signer** | A cryptographic identity authorized to approve actions on a wallet. Signers are separate from the wallet itself and can be added, removed, or rotated without changing the wallet's address. See [Signers](/wallets/concepts/signers). |
| **Operational signer** | A signer added to a wallet for day-to-day transaction authorization. Operational signers are registered by the recovery signer and can be scoped with specific permissions. Examples: a device key for user wallets, a server key for company wallets. See [Signers](/wallets/concepts/signers). |
| **Delegated signer** | The previous term for **operational signer**. Still used in current SDK method names (`addDelegatedSigner`, `delegatedSigners`) but being renamed in the next breaking release. |
| **Recovery signer** | A signer used to regain access to a wallet when the operational signer is lost — for example, when a user gets a new phone. Recovery signers authorize the enrollment of a new operational signer without changing the wallet address. See [Signers](/wallets/concepts/signers). |
| **Admin signer** | The previous term for **recovery signer**. You may still encounter this term in older SDK versions and API responses. |
| **Custody** | An entity has custody of a wallet if it can **unilaterally execute transactions** on the wallet or **unilaterally block transactions** on the wallet. See [Custody Models](/wallets/concepts/custody-models) for full details. |
| **Non-custodial** | A wallet configuration where your company cannot unilaterally move or block funds. Users control their own signers. See [Custody Models](/wallets/concepts/custody-models). |
| **Wallet locator** | A human-readable identifier for a wallet, following the format `email:user@example.com:evm` or `userId:abc123:solana`. Used in API calls instead of raw wallet addresses. |
| **Signer locator** | A human-readable identifier for a signer, such as `external-wallet:0x1234...` or `email:user@example.com`. Used when registering or referencing signers in API calls. |
| **Secure enclave** | The secure storage built into a user's phone, computer, or browser — such as the iOS Secure Enclave, Android Keystore, or the browser's built-in credential store. Cryptographic keys generated inside a secure enclave are non-extractable and never leave the device. Crossmint uses device secure enclaves to back [device key signers](/wallets/guides/signers/device-signer). |
| **TEE (Trusted Execution Environment)** | A hardware-attested secure enclave (e.g., Intel TDX) where sensitive cryptographic operations occur. Crossmint uses TEEs for email and SMS recovery signers — master secrets are derived inside the TEE and never exposed to Crossmint or the host application. |
| **Onchain** | Deployed to or enforced by the blockchain. In Crossmint wallets, signer permissions and wallet logic are onchain and publicly auditable. |
# Platform Overview
Source: https://docs.crossmint.com/introduction/platform-overview
Crossmint is an all-in-one platform to integrate [wallets](/wallets/overview), [stablecoins](/stablecoin-orchestration/overview), and other blockchain primitives into your product. Trusted by the world's largest financial institutions including MoneyGram and Santander Bank, and thousands of start-ups.
At its core, Crossmint abstracts the hardest parts of blockchain—wallets, keys, gas, compliance, and payments—into simple APIs, SDKs, and dashboards. This allows teams to focus on user experience and business logic while Crossmint handles the underlying complexity.
## How the platform is structured
Crossmint is designed as a modular platform composed of interoperable building blocks. You can use them independently or combine them into an end-to-end system:
| Product | Description |
| :--------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [Wallets](/wallets/overview) | Embedded wallets for user-facing applications and [treasury wallets](/wallets/concepts/treasury) for businesses. Wallets support flexible custody models, programmable permissions, and abstract away private keys and gas. Extensions such as [yield](/wallets/guides/wallet-extensions/yield-xyz), trading, and [cards](/wallets/guides/wallet-extensions/credit-cards) can be added on top of core wallet functionality. |
| [Payments](/stablecoin-orchestration/overview) | Stablecoin-based money movement, including [onramps](/onramp/overview) and offramps, [payouts and transfers](/stablecoin-orchestration/regulated-transfers/overview), and [checkout flows](/payments/introduction). These APIs enable accepting, sending, converting, and settling value across 150+ countries through a single integration. |
| [Tokenization](/minting/introduction) | Issuance and distribution of tokens at scale. |
The platform includes built-in compliance infrastructure across the regulated products, covering [KYC, AML and sanctions screening](/identity/overview), and travel rule compliance—so you can build compliant products without running these systems yourself.
## What you can build
Crossmint's building blocks are designed to be combined into complete, end-to-end solutions. Teams can mix and match wallets, payments, and embedded finance modules to support a wide range of products and workflows, like remittances and consumer fintech apps.
Browse guides tailored to your business vertical or use case
## How teams integrate
Crossmint is API-first, with dashboards for monitoring and daily operations.
For customers building complex systems, Crossmint provides **forward-deployed engineers** and **solution architects** who partner closely with teams to design architecture, payment flows, and compliance setup, reducing implementation risk and time to launch.
## What makes Crossmint unique
* **All-in-one platform**: You don't have to orchestrate 5-10 vendors. In addition to the products above, Crossmint saves you from separate integrations with RPCs, data APIs, AML screening providers, KYC, and other ancillary providers, offering everything you need in a fully integrated platform.
* **Make the blockchain invisible to your users**: No gas fees, passphrases, transaction approval prompts, or other blockchain gimmicks.
* **Easy-to-use APIs**: Don't require blockchain experience to get you started in minutes. Crossmint optionally takes care of gas fees and bills you in fiat, so you don't have to touch crypto or set up a treasury wallet.
* **No vendor lock-in**: Change providers anytime with minimal user disruption.
* **Bank-grade security**: Battle-tested and trusted by some of the largest financial institutions in the world.
## Learn more
View all 50+ supported chains
Get started in minutes
Talk to our team
# Account Verification
Source: https://docs.crossmint.com/introduction/platform/account-verification
Learn how to get your account verified
Access to certain Crossmint products is subject to compliance review.
| Product | Requirement |
| ------------------------ | -------------------------------------------------------------------------------------------------------------- |
| Wallets | No KYB required. |
| Stablecoin Orchestration | Signed Order Form and KYB required. |
| Onramps | Signed Order Form and KYB required. |
| Checkout | Signed Order Form and KYB required. Collection whitelisting also required for memecoins and primary NFT sales. |
| Tokenization | No KYB required. |
Reviews are only required in production. Staging does not require an Order Form, KYB, or collection review for any product.
Each distinct project you create via the Crossmint Console requires a separate review.
## How Production Access Works
For products that require it, production access follows the same sequence:
Work with the Crossmint sales team to sign an Order Form. The Order Form is the commercial agreement and includes the KYB obligation.
Crossmint's compliance team runs KYB (Know Your Business) verification as part of onboarding. The review covers entity formation, beneficial ownership, and business purpose.
Production is enabled once KYB passes. There is no separate request to make — a successful KYB unlocks the production environment for your account.
Staging is self-serve. You can build and test against staging with only a staging console account — no Order Form, no KYB, and no manual approval.
# Client-side Keys
Source: https://docs.crossmint.com/introduction/platform/api-keys/client-side
How to create and use client-side API keys
## Create a Client-side Key
Navigate to the API Keys section of the developer console and click the "Create new key" button in the client-side keys section.
* [Staging API Keys](https://staging.crossmint.com/console/projects/apiKeys)
* [Production API Keys](https://www.crossmint.com/console/projects/apiKeys)
### Setting Authorized Origins
Client-side keys are exposed in the application and thus require additional security measures. The minimum requirement is to whitelist URLs that requests can be sent from. Client-side keys support two types of origins:
You can only register either web origins or mobile app identifiers for a single API key, not both. If you need both
types, you'll need to create separate API keys.
#### Web Origins
For web applications, you need to add the domains that requests will be sent from. In development, you'll need to add the local domain you test your application from. This is commonly `http://localhost` followed by a port number such as `3000`, `5173`, etc. For example, when developing with NextJS, the default origin you need to authorize is `http://localhost:3000`.
The expected format for web origins is a full URL with protocol, such as `https://www.yourdomain.com` or `http://localhost:3000`.
#### Mobile App Identifiers
For mobile applications, you need to add the bundle identifiers for iOS apps or package names for Android apps. The expected format is:
* iOS: Bundle ID (e.g., `com.company.appname`)
* Android: Package name (e.g., `com.company.appname`)
Mobile app identifiers can be arbitrarily spoofed by malicious actors, as the headers sent from mobile applications can be modified. Do not rely solely on mobile app identifiers for security. Consider implementing additional authentication mechanisms such as JWT authentication for sensitive operations.
#### Desktop/CLI Applications
For desktop applications and CLI tools where origin headers cannot be reliably set, you can select the "Desktop/CLI" app type when creating your client-side API key. This option allows the key to be used without origin restrictions.
**Security Warning**: Desktop/CLI keys have no origin-based protection. This means:
* The key can be used from any origin or application
* If the key is leaked, it can be used by anyone without restriction
* Consider enabling JWT authentication for additional security
* Only use this option when absolutely necessary (e.g., CLI tools, desktop apps, AI agents)
For most use cases, we recommend using web origins or mobile app identifiers instead.
To create a Desktop/CLI key:
1. Select "Desktop/CLI" as the app type when creating or editing a client-side key
2. Consider enabling JWT authentication for additional security
3. Store the key securely and rotate it regularly
When you create a production API key you will need to authorize your production domain or app identifiers to use the API key. You can add multiple authorized domains or app identifiers for an API key to make requests from. Type in the domain or app identifier that you want to authorize and then click the "+ Add new origin" button.
### Select Scopes
Within the modal that opens, toggle the required scopes you want to enable. You may need to expand an accordion for the product area you're working on to expose additional scope options.
For more information on API Key scopes visit the scopes page or the API Reference.
Complete list of available API scopes
Detailed docs for all API endpoints
### JWT Authentication
Finally, select the option to require a JWT if your application or use case requires it. Enabing this setting will require that users are authenticated to permit API requests.
The [Wallets SDK](/sdk-reference/wallets/typescript/overview) requires this option to be enabled. It is optional for other
client side APIs. For more information on the options, refer to the [JWT
Authentication](/introduction/platform/api-keys/jwt-authentication) section.
If you choose to enable JWT Authentication for your client-side API key, there are additional configurations that must be made. You can choose between Crossmint authentication (ideal for staging and getting started fast), third party auth providers such as Auth0, Stytch, Firebase, Kinde, or Supabase (recommended for production), or custom solutions where you generate your own JWTs (also recommended for production).
You can find more information and guidance in the [JWT Authentication](/introduction/platform/api-keys/jwt-authentication) section.
***
## Using a Client-side Key
There are a few different approaches to using a client-side key. The most common option is passing it to the `init` function for a supported SDK. There are also some cases where you'll pass the key as a header in a raw API call from custom code, similar to how a server-side key works.
### Initializing an SDK
The most common way you'll leverage a client-side API key is by passing it to the `init` function for a supported SDK. See the examples below.
```typescript crossmint-wallets-sdk.ts theme={null}
import { CrossmintWallets, createCrossmint } from "@crossmint/wallets-sdk";
const crossmint = createCrossmint({
apiKey: process.env.NEXT_PUBLIC_CLIENT_SIDE_KEY ?? "",
});
const crossmintWallets = CrossmintWallets.from(crossmint);
const wallet = await crossmintWallets.getOrCreateWallet({
chain: "",
signer: {
type: "",
},
});
```
```typescript credentials-init.ts theme={null}
import * as sdk from '@crossmint/client-sdk-verifiable-credentials';
sdk.CrossmintAPI.init(process.env.NEXT_PUBLIC_CLIENT_SIDE_KEY);
```
```tsx auth-example.tsx theme={null}
import { CrossmintProvider, CrossmintAuthProvider } from "@crossmint/client-sdk-react-ui";
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
{children}
);
}
```
### Direct API Call From Client
The Headless Checkout is one example where you may be writing custom API calls from your application to create orders. In this case, you set the client-side API key as a header named `X-API-KEY`, much like you would when making a server-side API call.
```typescript theme={null}
const createOrder = async (orderInput: any) => {
try {
const res = await fetch(`https://staging.crossmint.com/api/2022-06-09/orders`, {
method: "POST",
headers: {
"Content-Type": "application/json",
"x-api-key": process.env.NEXT_PUBLIC_CLIENT_SIDE_KEY,
},
body: JSON.stringify(orderInput),
});
const order = await res.json();
setOrder(order.order);
setClientSecret(order.clientSecret);
} catch (e) {
console.error(e);
throw new Error("Failed to create order");
}
};
```
# JWT Authentication
Source: https://docs.crossmint.com/introduction/platform/api-keys/jwt-authentication
Enable advanced security for client-side API Keys
JWT-based authentication is a mechanism in which you can authorize a call to a Crossmint API, scoping such calls to impact resources related to a single end-user on your platform. You have three options available when enabling this option.
1. **Crossmint Authentication** - A quick-to-integrate auth solution, ideal for staging environments and getting started fast. Crossmint handles the generation of JWTs and you're able to quickly create a working application with user access controls. **For production, options 2 or 3 below are recommended.**
2. **Third Party Authentication providers** *(recommended for production)* - There are several quality authentication providers in the market and you may already be using one of them. You can continue to use your existing auth tooling and enable Crossmint Wallets on top of them with minimal changes. Supported providers include Auth0, Firebase, Supabase, Stytch, and Kinde.
3. **Custom JWT** *(recommended for production)* - This option is for providers not yet included with turn-key support or custom built solutions where you issue your own JWTs. You must be able to provide a JWKS endpoint to enable Crossmint to authenticate tokens.
Crossmint's Wallets SDK ***requires*** JWT Authentication be enabled.
## Crossmint Authentication
This option makes the most sense if you're getting started quickly and want to prototype or test in a staging environment. Crossmint Auth is great for demos and quickstarts, but for production applications a [third-party auth provider](/wallets/guides/bring-your-own-auth) or custom JWT is recommended for full control over your user management.
To use this option, select the Crossmint Auth option within the JWT Authentication section of the API keys page in the Developer Console.
## Third Party Authentication
For projects that have already implemented authentication using one of the supported providers, you can provide Crossmint with the appropriate project/environment/app ID from the settings page of the third party. Supported providers include: Dynamic, Auth0, Stytch, Privy, Firebase, Kinde, and Supabase.
To use this option, select the 3P Auth providers option, then choose your provider from the dropdown. Next, you'll need to find the appropriate ID that your provider gives you to identify your project. This can typically be found within the settings pane of their web interface.
## Custom JWT Auth
If your project is using an auth provider not listed in the 3P providers list or is a home-grown JWT solution, you can choose the Custom tokens option. You will need to be able to provide a JWKS endpoint that Crossmint can use to authenticate the JWTs.
For a detailed guide on implementing this approach, check out the [Custom JWT Authentication Guide](/introduction/platform/api-keys/custom-jwt-auth-guide).
# Overview
Source: https://docs.crossmint.com/introduction/platform/api-keys/overview
Get your keys in seconds and start building
API keys are required to authorize requests against the Crossmint APIs. By using an API key, Crossmint knows which project is making the call, and can deduct credits from your balance.
## Staging vs. Production Keys
First, determine if you need a staging (testing) or production API key.
Generate API keys for testing in the staging developer console
Generate API keys for going live in the production developer console
## Server-side vs. Client-side Keys
Server-side API keys are used in server-to-server communications or in code running on a server. These keys are not exposed to the end users and can have broader permissions because they are considered more secure, being stored and used in controlled environments.
The majority of Crossmint APIs require a server-side API key. For a comprehensive list of APIs available refer to
the [API Reference](/api-reference/introduction).
Client-side API keys are used in code that runs on the client-side, such as in web browsers or mobile apps. These keys are exposed to the end user and are therefore less secure. They typically have more restrictive permissions to minimize security risks. When creating a client-side API key, you also need to configure authorized origins that are allowed to make calls to the endpoint.
Client-side keys are required for building with the [Wallets SDK](/sdk-reference/wallets/typescript/overview), Authentication, and [Headless Checkout](/payments/headless/overview).
You can also perform some custodial wallet actions with these key types. Finally, the [Verifiable Credentials SDK](/verifiable-credentials/introduction) also offers some features via client-side keys.
## More information
Information about standard rate limits and exceptions
Complete list of available API scopes
How to create and use server-side API keys
How to create and use client-side API keys
# Rate Limits
Source: https://docs.crossmint.com/introduction/platform/api-keys/rate-limits
Understand the throughput available for API calls
Rate limits prevent individual users from clogging the network. If a limit is exceeded, Crossmint will respond with HTTP 429 and require a brief waiting period before making additional requests.
The rate limits on self-serve plans are the following:
To increase the base rate limits, [contact us](https://www.crossmint.com/contact/sales).
### Base Limits
| Method(s) | Rate Limit |
| :-------- | :---------------------------------- |
| POST | 120 requests per minute per project |
| PUT | Same as above |
| PATCH | Same as above |
| DELETE | Same as above |
| GET | 360 requests per minute per project |
### Exceptions
| API | Route | Method | Rate Limit |
| :---------------------------------------------------------------------------------- | :---------------------------------------------------------- | :----- | :------------------ |
| [Mint NFT](/api-reference/minting/nfts/mint-nft) | /api/2022-06-09/collections/\{collectionId}/nfts | POST | 600 req/min/project |
| [Mint NFT (Idempotent)](/api-reference/minting/nfts/mint-nft-idempotent) | /api/2022-06-09/collections/\{collectionId}/nfts/\{nftName} | PUT | 600 req/min/project |
| [Collection Info](/api-reference/minting/collection/get-collection) | /api/2022-06-09/collections/\{collectionId} | GET | 600 req/min/project |
| [Mint Status](/api-reference/minting/nfts/mint-status) | /api/2022-06-09/collections/\{collectionId}/nfts/\{id} | GET | 600 req/min/project |
| [Get All Tokens](/api-reference/headless/get-all-tokens) | /api/2024-09-26/tokens | GET | 50 req/min/project |
| [Get User](/api-reference/users/get-user) | /api/2025-06-09/users/\{userLocator} | GET | 80 req/min/project |
| [Create or Update User](/api-reference/users/upsert-user) | /api/2025-06-09/users/\{userLocator} | PUT | 50 req/min/project |
| [Get Identity Verification](/api-reference/users/get-identity-verification) | /api/2025-06-09/users/\{userLocator}/identity-verification | GET | 80 req/min/project |
| [Trigger Identity Verification](/api-reference/users/trigger-identity-verification) | /api/2025-06-09/users/\{userLocator}/identity-verification | PUT | 50 req/min/project |
| [Accept Legal Document](/api-reference/users/accept-legal-document) | /api/2025-06-09/users/\{userLocator}/legal-documents | PUT | 50 req/min/project |
| [Upload User Document](/api-reference/users/upload-document) | /api/2025-06-09/documents | POST | 50 req/min/project |
| [Get User Document](/api-reference/users/get-document) | /api/2025-06-09/documents/\{documentId} | GET | 80 req/min/project |
# Scopes
Source: https://docs.crossmint.com/introduction/platform/api-keys/scopes
Enabling required permissions for API calls
Below is a complete list of the API scopes available. You can also find the scope a specific API requires in the [API Reference](/api-reference/introduction) section.
## Wallet APIs
| Scope | Description | Server Key | Client Key |
| ----------------------------- | -------------------------------------------------- | ---------- | ---------- |
| `wallets.read` | Retrieve all wallets for a user. | ✅ | ✅ |
| `wallets.create` | Create a wallet for a user. | ✅ | ✅ |
| `wallets:nfts.read` | Fetch the NFTs owned by a specific wallet address. | ✅ | ✅ |
| `wallets:balance.read` | Get the balance of a specific wallet address. | ✅ | ✅ |
| `wallets:transactions.create` | Create a transaction from a user's wallet. | ✅ | ✅ |
| `wallets:transactions.sign` | Sign a transaction from a user's wallet. | ✅ | ✅ |
| `wallets:transactions.read` | Read transactions from a user's wallet. | ✅ | ✅ |
| `wallets:signatures.create` | Create a signature for a wallet. | ✅ | ✅ |
| `wallets:signatures.read` | Read a signature for a wallet. | ✅ | ✅ |
| `wallets.fund` | Send funds to a wallet. | ✅ | ✅ |
| `wallets.export` | Export a wallet's encrypted private key material. | | ✅ |
| `wallets:nfts.transfer` | Transfer an NFT from a user's wallet. | ✅ | |
| `wallets:messages.sign` | Sign a message from a user's wallet. | ✅ | |
When using the [Wallets SDK](/sdk-reference/wallets/typescript/overview) you ***must*** use a client-side API key.
## Authentication
| Scope | Description | Server Key | Client Key |
| -------------- | ------------------------------------- | ---------- | ---------- |
| `users.create` | Create users / allow them to sign up. | | ✅ |
| `users.read` | Get profile info for user accounts. | | ✅ |
## Tokenization (Minting) APIs
| Scope | Description | Server Key | Client Key |
| ------------------------------ | -------------------------------------------------------------------------- | ---------- | ---------- |
| `nfts.create` | Mint your NFTs and deliver them to a wallet or to an email address. | ✅ | |
| `nfts.update` | Update a minted NFT's metadata on IPFS (image, description, name...). | ✅ | |
| `nfts.read` | Retrieve all metadata for an NFT. | ✅ | |
| `nfts.delete` | Burn a specific NFT within a collection. | ✅ | |
| `nfts.transfer` | Transfer an NFT to a different wallet. | ✅ | |
| `collections.create` | Create a collection of NFTs. | ✅ | |
| `collections.update` | Update information for an existing collection (image, name, royalties...). | ✅ | |
| `collections.read` | Retrieve the information about a specific collection. | ✅ | |
| `credentials.read` | Fetch credentials, some endpoints will only work with a server side key. | ✅ | ✅ |
| `credentials.decrypt` | Decrypt credentials, mainly used by our client side SDK. | ✅ | ✅ |
| `credentials:templates.create` | Create a template for your credentials. | ✅ | |
| `credentials.create` | Issue your credentials and create credential types | ✅ | |
| `credentials.delete` | Revoke a credential issued to a subject. | ✅ | |
## Checkout APIs
| Scope | Description | Server Key | Client Key |
| --------------- | ----------------------------------------------- | ---------- | ---------- |
| `orders.create` | Create an order for headless checkout. | ✅ | ✅ |
| `orders.read` | Get an existing order for headless checkout. | ✅ | ❌ |
| `orders.update` | Update an existing order for headless checkout. | ✅ | ❌ |
Client-side API keys only have access to the `orders.create` scope. When reading or updating order status from the
client-side, you must pass the `clientSecret` returned in the create-order call as an `authorization` header for
subsequent order operations (get-order, update-order). The `clientSecret` provides the authorization and an API Key
is not required in this use case. See [this
guide](/payments/headless/guides/client-or-server#client-side-example-code) in the Headless Checkout docs.
## Project Administration
| Scope | Description | Server Key | Client Key |
| --------------------- | ------------------------------------------------- | ---------- | ---------- |
| `billing.readonly` | Get balance in credits for a project. | ✅ | |
| `projects:usage.read` | Get usage for the different products in a project | ✅ | |
# Server-side Keys
Source: https://docs.crossmint.com/introduction/platform/api-keys/server-side
How to create and use server-side API keys
**Server-side API keys must be stored securely!**
Enable only the scopes you need, and no more, and do NOT expose your keys on the frontend of your app, or your github code repository.
## Create a Server-side Key
Navigate to the API Keys section of the developer console and click the "Create new key" button in the server-side keys section.
* [Staging API Keys](https://staging.crossmint.com/console/projects/apiKeys)
* [Production API Keys](https://www.crossmint.com/console/projects/apiKeys)
### Select Scopes
**Production server-side keys follow a "view once" policy**
When you create a server-side API key in the production environment, the key secret will only be shown once during creation. After you close the dialog or navigate away, **you will not be able to view the key secret again.**
Within the modal that opens, toggle the required scopes on and click the "Create server key" button at the bottom. You may need to expand an accordion for the product area you're working on to expose additional scope options.
You can determine the scopes you need by visiting the API reference page for the API(s) you need to interact with.
Complete list of available API scopes
Detailed docs for all API endpoints
## Using a Server-side Key
If you're using the [API Playground](/api-reference) you can add your API key in the Authorization section and this will set it as a header when making the request.
When calling the APIs from server-side code, you set the `X-API-KEY` header however the target language or library expects it. See some examples below:
```bash cURL theme={null}
curl --request POST \
--url https://staging.crossmint.com/api/v1-alpha1/wallets \
--header 'Content-Type: application/json' \
--header 'X-API-KEY: YOUR_API_KEY' \
--data '{
"email": "test@test.com",
"chain": "polygon-amoy"
}'
```
```javascript Node.js theme={null}
const options = {
method: 'POST',
headers: {
'X-API-KEY': 'YOUR_API_KEY',
'Content-Type': 'application/json'
},
body: '{
"email":"test@test.com",
"chain":"polygon-amoy"
}'
};
fetch('https://staging.crossmint.com/api/v1-alpha1/wallets', options)
.then(response => response.json())
.then(response => console.log(response))
.catch(err => console.error(err));
```
All of the API routes in the [API Reference](/api-reference/introduction) include code examples for many popular programming languages.
# Project and Team Management
Source: https://docs.crossmint.com/introduction/platform/projects-and-user-management
Manage your projects and their members
## Managing Projects
A project is a group of token collections, user wallets, API keys, billing information, and more.
If you are working with different teams or clients, you can create different projects to seggregate sensitive information and tool access. Alternatively, you can issue different API keys within the same project, but bear in mind they will share the same billing account.
Every developer account automatically includes a project named `Default Project`. You can create new projects and alternate between them using the selector within the console, as illustrated in the screenshot below.
## Privacy Policy
Projects have an option to register a link to your company's privacy policy. If set, this link will be shown to your users when they're using any Crossmint-managed user interface, such as the Checkout, where user information is collected. A privacy policy is required by law if you want to later those users' personally identifiable information, such as their email address.
If you don't already have a privacy policy, you can generate and host one using tools like [Termly](https://termly.io/).
> **Note:** Failure to add a privacy policy will result in email addresses being hidden.
#### Cases Requiring a Privacy Policy to Access User Data
| Use Case | Privacy Policy Required |
| ---------------------------- | ----------------------- |
| Payments (payment button) | Yes |
| Payments (embedded checkout) | Yes |
| Payments (headless checkout) | No |
| Mint API or console | No |
| Wallet APIs or console | No |
### Steps to Add a Privacy Policy Link
1. \**Go to the General Settings tab*
* Access the Settings page from the sidear.
2. **Include the Privacy Policy Link**
* In the project settings, locate the section for adding a privacy policy.
* Paste the URL of your privacy policy generated from Termly or any other source.
4. **Verify Email Visibility**
* After adding the privacy policy link, check to ensure that email addresses are now visible.
* If the link is not included, emails will remain hidden to protect user data.
Following these steps will ensure compliance with data protection laws and enable the visibility of email addresses in your project settings.
***
## FAQs
Crossmint requires your project's privacy policy to comply with data protection laws and ensure the privacy and
security of user data. Without it, email addresses and other sensitive information will be hidden.
Older users who joined before the privacy policy was added haven't accepted the new policy. Therefore, their
identifiers remain hidden.
Deleting the privacy policy is restricted to ensure compliance with data protection laws and maintain user
privacy and security.
Projects can be shared across multiple team members. You can add new members from the Members tab on the console. All members get the same permissions, except for the ability to delete other members, which only belongs to the creator of the project.
In order to invite a member to a project, they must already have a Crossmint account within that same environment.If you need to remove a team member, submit a ticket with Crossmint support.
# Staging vs Production Environments
Source: https://docs.crossmint.com/introduction/platform/staging-vs-production
Crossmint's console and first-party wallet operate in two different environments:
* **Staging:** free, for testing.
* **Production:** for your live projects. Blockchain operations incur credit costs.
You may access them from [https://staging.crossmint.com](https://staging.crossmint.com), and [https://www.crossmint.com](https://www.crossmint.com).
Staging provides free self-serve access to most functionality.
## Differences between the Environments
| | **Staging** | **Production** |
| ------------ | -------------------------------------------------------------- | ------------------------------------------------------ |
| URL | [https://staging.crossmint.com](https://staging.crossmint.com) | [https://www.crossmint.com](https://www.crossmint.com) |
| Payments | Test credit cards | Accepts real credit cards |
| API Credits | Free to test (no API credits required) | API credits required for some endpoints |
| Verification | Checkout does not require KYC or collection verification | Checkout requires KYC and collection verification |
| Blockchains | Testnets like Sepolia, and Amoy | Mainnets |
## FAQ
No, collections created in one environment will not appear in the other.
Typically, you would start developing your project in staging, and replicate and launch in production when you feel ready.
# Add an Endpoint
Source: https://docs.crossmint.com/introduction/platform/webhooks/add-endpoint
Configure an endpoint to receive webhook messages.
To add an endpoint, provide a URL you control and select the event types you want to listen to.
1. Navigate to the Webhooks page in the console.
2. Click **Add Endpoint**.
3. Provide the URL where you want to receive messages.
4. Select the event types you want to listen to.
5. Click **Create**.
### FAQs
There are many events available to subscribe to from all our products. Some examples include:
* Collection creation
* NFT minting
* NFT edits
* Transaction confirmations
* Transaction failures
You can see the full list of events in the Crossmint Console, under Integrate > Webhooks and the **Event Catalog** tab.
To verify a webhook request is legitimate, you need to verify the signature and timestamp. You can learn more about it in the [Verify webhooks](/introduction/platform/webhooks/verify-webhooks) section.
Crossmint automatically retries webhooks if your endpoint doesn't acknowledge its receipt, or throws an error. We will attempt to deliver the webhook 8 times:
* Immediately
* 5 seconds
* 5 minutes
* 30 minutes
* 2 hours
* 5 hours
* 10 hours
* 10 hours (in addition to the previous)
If, after these attempts, we're unable to deliver the message, we will mark it as failed. Inside the Webhooks page, you can manually resend the webhook.
To indicate that a webhook has been processed, return a 2xx (status code 200-299) response to the webhook message within a 15 seconds timeframe.
Some typical reasons why webhooks fail are:
* Check that the enpdpoint URL is correct and that it's expecting a POST request
* Check that the endpoint is reachable from the public internet. Make sure that CSRF protection is disabled for this endpoint.
* Check that the endpoint is returning a 2xx response code
* Check that the payload signature and timestamp are verified correctly. Remember not to modify the body string of the webhook before processing it.
# Best Practices
Source: https://docs.crossmint.com/introduction/platform/webhooks/best-practices
Best practices for implementing reliable webhook handlers
Follow these best practices to ensure your webhook implementation is reliable, secure, and performant.
## Idempotency
Crossmint webhooks follow an at-least-once delivery guarantee. Duplicate deliveries can occur (for example, if a previous attempt didn't receive a 2xx response quickly enough). Implement idempotency by:
1. Using the webhook `id` field as a unique identifier
2. Storing processed webhook IDs in your database
3. Checking if a webhook has already been processed before taking action
> **Note:** The following code examples are pseudocode for illustration purposes.
```javascript theme={null}
async function handleWebhook(webhookId, data) {
// Check if we've already processed this webhook
const existing = await db.webhooks.findOne({ id: webhookId });
if (existing) {
console.log('Webhook already processed:', webhookId);
return;
}
// Process the webhook
await processWebhook(data);
// Mark as processed
await db.webhooks.create({ id: webhookId, processedAt: new Date() });
}
```
## Response Time
Your webhook endpoint must respond within 15 seconds, otherwise the webhook will be resent. For long-running operations:
If your endpoint doesn't return a 2xx status code within 15 seconds, the delivery attempt is marked as failed and Crossmint will automatically retry with exponential backoff. This can result in duplicate deliveries (at-least-once semantics). After repeated failures, the message is marked as undeliverable and you can manually trigger redelivery from the Console. See [Add an Endpoint](/introduction/platform/webhooks/add-endpoint) for the full retry schedule.
1. Immediately acknowledge the webhook with a 200 response
2. Queue the processing work for asynchronous execution
3. Process the webhook data in a background job
> **Note:** The following code examples are pseudocode for illustration purposes.
```javascript theme={null}
app.post('/webhooks/your-endpoint', async (req, res) => {
// Verify signature first
const payload = verifyWebhook(req);
// Immediately acknowledge receipt
res.status(200).json({ received: true });
// Queue for background processing
await jobQueue.add('process-webhook', {
webhookId: payload.id,
type: payload.type,
data: payload.data,
});
});
```
## Error Handling
Implement proper error handling to ensure reliable webhook delivery:
* **Return 2xx within 15 seconds** after verifying the signature to acknowledge receipt
* **Return 400** for invalid signatures to reject the webhook
* **Return 5xx** for transient internal errors to trigger automatic retries
See [Verify Webhooks](/introduction/platform/webhooks/verify-webhooks) for more details on signature verification and error handling.
## Signature Verification
Always verify webhook signatures to ensure requests are legitimate and come from Crossmint. Never process webhooks without verifying their signatures first.
See [Verify Webhooks](/introduction/platform/webhooks/verify-webhooks) for detailed instructions on signature verification.
## Related Resources
* [Add an Endpoint](/introduction/platform/webhooks/add-endpoint) - Configure webhook endpoints
* [Verify Webhooks](/introduction/platform/webhooks/verify-webhooks) - Verify webhook signatures
* [Overview](/introduction/platform/webhooks/overview) - Learn about webhooks
# Overview
Source: https://docs.crossmint.com/introduction/platform/webhooks/overview
Listen for events across wallets, payments, minting, and credentials to automatically trigger reactions in your application
Crossmint's platform operations—including wallet transactions, payment processing, NFT minting, and credential issuance—often require blockchain interactions or other asynchronous processing. These operations can take anywhere from a few seconds to several minutes depending on network conditions. Webhooks allow you to listen for events across Crossmint's platform and automatically trigger reactions in your application when these operations complete.
Some cases where you may want to listen for event notifications include:
* **Wallet operations**: Track transfer approvals, transaction completions, failures, or private key exports
* **Payment processing**: Monitor checkout lifecycle from quote creation to delivery completion
* **Minting operations**: Get notified when mints succeed, collections are created, or tokens are transferred
* **Credential issuance**: Receive updates when verifiable credentials are successfully issued
* **Database updates**: Automatically update your records with transaction IDs, token details, or user information
* **Customer notifications**: Send emails or push notifications when operations complete
Webhooks are how services notify each other of events. At their core, they are simply POST requests to a pre-determined endpoint. The endpoint can be any URL you choose, and you can [add them from the console](/introduction/platform/webhooks/add-endpoint).
Your server must return a 2xx HTTP status quickly so the webhook is marked as delivered.
## Next Steps
* [Add an Endpoint](/introduction/platform/webhooks/add-endpoint) - Configure your webhook endpoint
* [Verify Webhooks](/introduction/platform/webhooks/verify-webhooks) - Learn how to verify webhook signatures
* [Best Practices](/introduction/platform/webhooks/best-practices) - Implement reliable webhook handlers
# Verify Webhooks
Source: https://docs.crossmint.com/introduction/platform/webhooks/verify-webhooks
Verify the signature and timestamp when processing webhooks
Because of the way webhooks work, attackers can impersonate services by simply sending a fake webhook to an endpoint. Think about it: it’s just an HTTP POST from an unknown source. This can create a potential security vulnerability for many applications, or at the very least, a source of errors.
To prevent this, Crossmint signs every webhook and its metadata with a unique key for each endpoint. Use this signature to verify that the webhook indeed comes from Crossmint, and process it only if it does.
You can get the signing key secret from the Webhooks page in the console. To find it, go to the endpoint details and look for the Signing Secret section.
We are going to use the Svix open-source library to verify webhooks. First, install the relevant libraries for your language:
```js JavaScript theme={null}
npm install svix
// Or
yarn add svix
```
```python Python theme={null}
pip install svix
```
```rust Rust theme={null}
http = "1.0.0"
svix = "1.20.0"
```
```go Go theme={null}
go get github.com/svix/svix-webhooks/go
```
```java Java theme={null}
// Gradle: Add this dependency to your project's build file:
implementation "com.svix:svix:0.x.y"
// Maven: Add this dependency to your project's POM file:
com.svixsvix0.x.y
```
```kotlin Kotlin theme={null}
// Gradle: Add this dependency to your project's build file:
implementation "com.svix.kotlin:svix-kotlin:0.x.y"
// Maven: Add this dependency to your project's POM file:
com.svix.kotlinsvix-kotlin0.x.y
```
```ruby Ruby theme={null}
gem install svix
```
```csharp C# theme={null}
dotnet add package Svix
```
```php PHP theme={null}
composer require svix/svix
```
```bash CLI theme={null}
# On macOS install via Homebrew:
brew install svix/svix/svix
# On Linux install via Scoop:
scoop bucket add svix https://github.com/svix/scoop-svix.git
scoop install svix
```
Next, verify webhooks using the code below. The payload is the raw (string) body of the request, and the headers are the headers passed in the request.
You need to use the **raw request body** when verifying webhooks, as the cryptographic signature is sensitive to even the slightest changes. Watch out for frameworks that parse the request as JSON and then stringify it, as this will break the signature verification.
See examples below for how to get the raw request body with different frameworks.
Remember to get the signature secret from the endpoint details in the console.
```js JavaScript theme={null}
import { Webhook } from "svix";
const secret = "whsec_MfKQ9r8GKYqrTwjUPD8ILPZIo2LaLaSw";
// These were all sent from the server
const headers = {
"svix-id": "msg_p5jXN8AQM9LWM0D4loKWxJek",
"svix-timestamp": "1614265330",
"svix-signature": "v1,g0hM9SsE+OTPJTGt/tmIKtSyZlE3uFJELVlNIOLJ1OE=",
};
const payload = '{"test": 2432232314}';
const wh = new Webhook(secret);
// Throws on error, returns the verified content on success
const payload = wh.verify(payload, headers);
```
```python Python theme={null}
from svix.webhooks import Webhook
secret = "whsec_MfKQ9r8GKYqrTwjUPD8ILPZIo2LaLaSw"
# These were all sent from the server
headers = {
"svix-id": "msg_p5jXN8AQM9LWM0D4loKWxJek",
"svix-timestamp": "1614265330",
"svix-signature": "v1,g0hM9SsE+OTPJTGt/tmIKtSyZlE3uFJELVlNIOLJ1OE=",
}
payload = '{"test": 2432232314}'
wh = Webhook(secret)
# Throws on error, returns the verified content on success
payload = wh.verify(payload, headers)
```
```rust Rust theme={null}
use svix::webhooks::Webhook;
let secret = "whsec_MfKQ9r8GKYqrTwjUPD8ILPZIo2LaLaSw".to_string();
let mut headers = http::HeaderMap::new();
headers.insert("svix-id", "msg_p5jXN8AQM9LWM0D4loKWxJek");
headers.insert("svix-timestamp", "1614265330");
headers.insert(
"svix-signature",
"v1,g0hM9SsE+OTPJTGt/tmIKtSyZlE3uFJELVlNIOLJ1OE=",
);
let payload = b"{\"test\": 2432232314}";
let wh = Webhook::new(&secret)?;
wh.verify(&payload, &headers)?;
// returns Ok on success, Err otherwise
```
```go Go theme={null}
import (
svix "github.com/svix/svix-webhooks/go"
)
secret := "whsec_MfKQ9r8GKYqrTwjUPD8ILPZIo2LaLaSw"
// These were all sent from the server
headers := http.Header{}
headers.Set("svix-id", "msg_p5jXN8AQM9LWM0D4loKWxJek")
headers.Set("svix-timestamp", "1614265330")
headers.Set("svix-signature", "v1,g0hM9SsE+OTPJTGt/tmIKtSyZlE3uFJELVlNIOLJ1OE=")
payload := []byte(`{"test": 2432232314}`)
wh, err := svix.NewWebhook(secret)
err := wh.Verify(payload, headers)
// returns nil on success, error otherwise
```
```java Java theme={null}
import com.svix.Webhook;
String secret = "whsec_MfKQ9r8GKYqrTwjUPD8ILPZIo2LaLaSw";
// These were all sent from the server
HashMap> headerMap = new HashMap>();
headerMap.put("svix-id", Arrays.asList("msg_p5jXN8AQM9LWM0D4loKWxJek"));
headerMap.put("svix-timestamp", Arrays.asList("1614265330"));
headerMap.put("svix-signature", Arrays.asList("v1,g0hM9SsE+OTPJTGt/tmIKtSyZlE3uFJELVlNIOLJ1OE="));
HttpHeaders headers = HttpHeaders.of(headerMap, BiPredicate)
String payload = "{\"test\": 2432232314}";
Webhook webhook = new Webhook(secret);
webhook.verify(payload, headers)
// throws WebhookVerificationError exception on failure.
```
```kotlin Kotlin theme={null}
import com.svix.kotlin.Webhook
val secret = "whsec_MfKQ9r8GKYqrTwjUPD8ILPZIo2LaLaSw";
// These were all sent from the server
val headersMap = mapOf(
"svix-id" to listOf("msg_p5jXN8AQM9LWM0D4loKWxJek"),
"svix-timestamp" to listOf("1614265330"),
"svix-signature" to listOf("v1,g0hM9SsE+OTPJTGt/tmIKtSyZlE3uFJELVlNIOLJ1OE=")
)
val headers = HttpHeaders.of(headersMap) { _, _ -> true }
val payload = "{\"test\": 2432232314}";
val webhook = Webhook(secret);
webhook.verify(payload, headers)
// throws WebhookVerificationError exception on failure.
```
```ruby Ruby theme={null}
require 'svix'
# These were all sent from the server
headers = {
"svix-id" => "msg_p5jXN8AQM9LWM0D4loKWxJek",
"svix-timestamp" => "1614265330",
"svix-signature" => "v1,g0hM9SsE+OTPJTGt/tmIKtSyZlE3uFJELVlNIOLJ1OE="
}
payload = '{"test": 2432232314}'
wh = Svix::Webhook.new("whsec_MfKQ9r8GKYqrTwjUPD8ILPZIo2LaLaSw")
# Raises on error, returns the verified content on success
json = wh.verify(payload, headers)
```
```csharp C# theme={null}
using Svix;
using System.Net;
// These were all sent from the server
var headers = new WebHeaderCollection();
headers.Set("svix-id", "msg_p5jXN8AQM9LWM0D4loKWxJek");
headers.Set("svix-timestamp", "1614265330");
headers.Set("svix-signature", "v1,g0hM9SsE+OTPJTGt/tmIKtSyZlE3uFJELVlNIOLJ1OE=");
var payload = "{\"test\": 2432232314}";
var wh = new Webhook("whsec_MfKQ9r8GKYqrTwjUPD8ILPZIo2LaLaSw/Je4ZJEGP1QFb");
// Throws on error
wh.Verify(payload, headers);
```
```php PHP theme={null}
// import using composers autoload
require_once('vendor/autoload.php');
// or manually
require_once('/path/to/svix/php/init.php');
// These were all sent from the server
$payload = '{"test": 2432232314}';
$header = array(
'svix-id' => 'msg_p5jXN8AQM9LWM0D4loKWxJek',
'svix-timestamp' => '1614265330',
'svix-signature' => 'v1,g0hM9SsE+OTPJTGt/tmIKtSyZlE3uFJELVlNIOLJ1OE=',
);
// Throws on error, returns the verified content on success
$wh = new \Svix\Webhook('whsec_MfKQ9r8GKYqrTwjUPD8ILPZIo2LaLaSw');
$json = $wh->verify($payload, $header);
```
```bash CLI theme={null}
export SVIX_AUTH_TOKEN="AUTH_TOKEN"
svix verify --secret whsec_MfKQ9r8GKYqrTwjUPD8ILPZIo2LaLaSw --msg-id msg_p5jXN8AQM9LWM0D4loKWxJek --timestamp 1614265330 --signature v1,g0hM9SsE+OTPJTGt/tmIKtSyZlE3uFJELVlNIOLJ1OE= '{"test": 2432232314}'
```
This is a manual verification process. We recommend using a library to verify webhooks automatically.
If you need to verify manually, follow these instructions.
Each webhook call includes three headers with additional information that are used for verification:
* `svix-id`: The unique message identifier for the webhook message. This identifier is unique across all messages, but will be the same when the same webhook is resent (e.g., due to a previous failure).
* `svix-timestamp`: Timestamp in seconds since epoch.
* `svix-signature`: The Base64 encoded list of signatures (space delimited).
### Constructing the Signed Content
The content to sign is composed by concatenating the ID, timestamp, and payload, separated by the full-stop character (`.`). In code, it will look something like this:
```js theme={null}
const signedContent = `${svix_id}.${svix_timestamp}.${body}`;
```
Where `body` is the raw body of the request. The signature is sensitive to any changes, so even a small change in the body will cause the signature to be completely different. This means that you should not change the body in any way before verifying.
Ensure you use the **raw request body** for verification. Parsing and re-stringifying JSON will alter the content and cause verification failure.
### Determining the Expected Signature
We use HMAC with SHA-256 to sign webhooks.
To calculate the expected signature, HMAC the `signedContent` from above using the base64 portion of your signing secret (this is the part after the `whsec_` prefix) as the key. For example, if your secret is `whsec_MfKQ9r8GKYqrTwjUPD8ILPZIo2LaLaSw`, use `MfKQ9r8GKYqrTwjUPD8ILPZIo2LaLaSw`.
For example, this is how you can calculate the signature in Node.js:
```js theme={null}
const crypto = require('crypto');
const signedContent = `${svix_id}.${svix_timestamp}.${body}`;
const secret = "whsec_5WbX5kEWLlfzsGNjH64I8lOOqUB6e8FH";
// Need to base64 decode the secret
const secretBytes = Buffer.from(secret.split('_')[1], "base64");
const signature = crypto
.createHmac('sha256', secretBytes)
.update(signedContent)
.digest('base64');
console.log(signature);
```
This generated signature should match one of the ones sent in the `svix-signature` header.
The `svix-signature` header is a list of space-delimited signatures with version identifiers. The list is typically one item but can contain multiple signatures, like this:
```
v1,g0hM9SsE+OTPJTGt/tmIKtSyZlE3uFJELVlNIOLJ1OE= v1,bm9ldHUjKzFob2VudXRob2VodWUzMjRvdWVvdW9ldQo= v2,MzJsNDk4MzI0K2VvdSMjMTEjQEBAQDEyMzMzMzEyMwo=
```
Make sure to remove the version prefix and delimiter (e.g. `v1,`) before verifying the signature.
Please note that to compare the signatures it's recommended to use a constant-time string comparison method in order to prevent timing attacks.
### Verify timestamp
We include the timestamp of the attempt in the `svix-timestamp` header. Compare this timestamp against your system timestamp to ensure it’s within an acceptable tolerance to prevent replay attacks.
# Supported Chains
Source: https://docs.crossmint.com/introduction/supported-chains
Tap into 50+ blockchains in one single integration
Refer to the table below to see which chains are supported for which product categories.
| Blockchain | Wallets | Checkout | Onramp | Tokenization | Mainnet Label | Testnet Label |
| :---------------- | :-----: | :------: | :----: | :----------: | :----------------: | :------------------------: |
| Apex | - | ✅ | ✅ | - | `apex` | - |
| Abstract | ✱ | ✅ | ✱ | ✱ | `abstract` | `abstract-testnet` |
| Atleta | ✱ | ✅ | ✅ | ✅ | `atleta` | `atleta-testnet` |
| ApeChain | ✅ | ✅ | ✅ | ✅ | `apechain` | `curtis` |
| Arc | ✅ | ✱ | ✱ | ✱ | `arc` | `arc-testnet` |
| Aptos | ✱ | ✱ | ✱ | ✅ | `aptos` | `aptos` |
| Arbitrum One | ✅ | ✅ | ✅ | ✅ | `arbitrum` | `arbitrum-sepolia` |
| Arbitrum Nova | ✅ | ✅ | ✅ | ✅ | `arbitrumnova` | `arbitrum-sepolia` |
| Astar | ✱ | ✅ | ✅ | ✅ | `astar-zkevm` | `zkyoto` |
| Avalanche | ✱ | ✅ | ✅ | ✅ | `avalanche` | `avalanche-fuji` |
| Avalanche Subnets | ✱ | ✱ | ✱ | ✱ | `avalanche-subnet` | `avalanche-subnet-testnet` |
| Base | ✅ | ✅ | ✅ | ✅ | `base` | `base-sepolia` |
| BSC | ✅ | ✱ | ✱ | ✱ | `bsc` | `bsc-testnet` |
| Chiliz | ✱ | ✅ | ✅ | ✅ | `chiliz` | `chiliz-spicy-testnet` |
| Coti | ✱ | ✅ | ✅ | ✅ | `coti` | `coti-testnet` |
| Ethereum | ✱ | ✅ | ✅ | ✱ | `ethereum` | `ethereum-sepolia` |
| Flow | ✱ | ✅ | ✱ | ✅ | `flow` | `flow-testnet` |
| Hedera EVM | ✱ | ✅ | ✅ | ✅ | `hedera` | `hedera-testnet` |
| Mantle | ✅ | ✅ | ✅ | ✅ | `mantle` | `mantle-testnet` |
| Mode | ✅ | ✅ | ✅ | ✅ | `mode` | `mode-sepolia` |
| Monad | ✱ | ✱ | ✱ | ✱ | `monad` | `monad-testnet` |
| Optimism | ✅ | ✅ | ✅ | ✅ | `optimism` | `optimism-sepolia` |
| Polygon | ✅ | ✅ | ✅ | ✅ | `polygon` | `polygon-amoy` |
| Rari | ✱ | ✅ | ✅ | ✅ | `rari` | `rari-testnet` |
| Sei | ✅ | ✅ | ✅ | ✅ | `sei-pacific-1` | `sei-atlantic-2-testnet` |
| Scroll | ✅ | ✅ | ✅ | ✅ | `scroll` | `scroll-sepolia` |
| Solana | ✅ | ✅ | ✅ | ✅ | `solana` | `solana` |
| Stellar | ✅ | ✱ | ✅ | ✱ | `stellar` | `stellar-testnet` |
| Shape | ✅ | ✅ | ✅ | ✅ | `shape` | `shape-sepolia` |
| SKALE | ✱ | ✅ | ✅ | ✅ | `skale-nebula` | `skale-nebula-testnet` |
| Soneium | ✱ | ✅ | ✅ | ✅ | `soneium` | `soneium-minato-testnet` |
| Story | ✅ | ✱ | ✱ | ✅ | `story-mainnet` | `story-testnet` |
| Sui | ✱ | ✱ | ✱ | ✅ | `sui` | - |
| Tempo | ✅ | ✱ | ✱ | ✱ | `tempo` | `tempo-testnet` |
| U2U | ✱ | ✅ | ✅ | ✅ | `u2u-solaris` | `u2u-nebulas` |
| Xion | ✱ | ✅ | ✅ | ✅ | `xion` | `xion-testnet` |
| Xai | ✱ | ✅ | ✅ | ✅ | `xai` | `xai-sepolia-testnet` |
| Zenchain | ✱ | ✅ | ✅ | ✅ | `zenchain` | `zenchain-testnet` |
| Zora | ✅ | ✅ | ✅ | ✅ | `zora` | `zora-sepolia` |
| Other EVM chains | ✱ | ✱ | ✱ | ✱ | | |
**Legend:**
* `✅` Supported
* `✱` Available, but not self-serve. [Contact us](https://www.crossmint.com/contact/sales) to request it for your project.
* `-` Not supported
Notes:
* Fungible token checkout available self service only in Solana. [Contact us](https://www.crossmint.com/contact/sales) for other chains.
Contact us. We launch new chains regularly and yours is likely in the pipeline.
## Testnets
You can access testnets by using the [staging console](https://staging.crossmint.com/console). Read more about the different environments [here](/introduction/platform/staging-vs-production).
Please refer to the supported chains table above for details about which testnet the staging environment targets. If there is not a specific testnet listed for a chain it is not supported in staging.
# Overview
Source: https://docs.crossmint.com/minting/introduction
Mint and distribute tokens at scale, reliably
**It takes months to build secure and reliable minting infrastructure:**
1. Write and maintain token smart contracts
2. Build a backend for orchestrating all blockchain transactions
3. Securely administer keys and crypto balances to pay for gas fees
4. Add queueing, batching, RPC redundancy, priority gas fee estimations, and e2e observability
**Crossmint manages all of this for you via a suite of APIs to create, update, delete and manage tokens of any type through REST APIs, available on any chain.**
## Key Characteristics
Simple APIs to help you launch faster.
Forget about smart contracts, IPFS, wallets, and admin key management.
Let Crossmint handle all the infrastructure, or combine it with your own.
SOC-II certified with audited smart contracts.
Handle millions of tokens an hour and save on customer support.
Tap into 50+ chains with one single integration.
## Quickstarts
Mint and verify unique digital assets you can own, transfer, and verify in under minutes.
Mint limited sets of identical digital assets in under 5 minutes.
Mint credentials that users control, share, and verify anywhere in under 5 minutes.
Mint IP credentials for creators to secure their work, in under 5 minutes.
## Get Started
Get API keys and manage collections.
Test any API in seconds directly from the docs.
Contact our sales team for advanced support.
## FAQs
No, you can migrate providers anytime, transfering ownership of the smart contracts to an external wallet.
Yes, you can transfer ownership at any time. However, note that Crossmint APIs (eg. minting, editing NFTs) may
stop working on that contract unless you whitelist Crossmint on the contract to continue executing those
actions. If you transfer ownership of the contract to your own wallet, you are responsible for ensuring the
security of that wallet.
When you create an account, Crossmint automatically creates a gas station to manage network fees on your behalf. You will be billed in fiat and Crossmint will ensure you never run out of funds to support customer operations. This eliminates the need to build a cryptoaccounting system, execute recurring and expensive crypto purchases, holding the FX risk, and automating treasury operations.
When you create an account, Crossmint will automatically create a dedicated MPC vault to secure your token contracts which you can manage via API. You get one vault per project, in case you are working with multiple teams. You can transfer ownership of the contracts to an external wallet at any time.
# Bring Your Own Contract
Source: https://docs.crossmint.com/minting/nfts/integrate/bring-your-own-contract
Use Crossmint's infrastructure with your own smart contract
Crossmint allows you to use your own smart contract while leveraging our infrastructure for minting NFTs. This guide will walk you through the process of integrating your custom smart contract with Crossmint.
## Prerequisites
Before you can use your own smart contract with Crossmint, ensure that:
1. Your smart contract has a **free minting function** that allows Crossmint to mint NFTs without paying any fees.
2. Your smart contract complies with the **ERC721 standard** (for NFTs) or **ERC1155 standard** (for SFTs).
Currently, this feature is only available for EVM chains. Support for Solana and other chains is coming soon.
## Integration Guide
### 1. Register Your Smart Contract
First, you need to register your smart contract at the Crossmint Console:
1. Navigate to **Token collections** > **New Collection**
2. Enter the required collection information
3. Select **Import an existing contract**
4. Pick the chain, provide the contract address, and fill in the necessary information (i.e. ABI, mint function)
5. Keep track of the Crossmint collection ID generated for future mint calls
### 2. Contract Review
After registering your contract, the Crossmint team will review it to ensure compatibility with our infrastructure. This typically takes 1-2 business days.
During the review process, we'll verify that your contract has the necessary free minting function and complies with
the required standards.
### 3. Mint NFTs
Once your contract is approved, you can start minting NFTs using Crossmint's [Mint API](/api-reference/minting/nfts/mint-nft) and passing the following:
1. `recipient`, which is the recipient's wallet/email/X account user locators
2. `contractArguments`, which is an object where the keys are the argument names from the mint function, and the values are their values. Omit the recipient argument from this object
```bash cURL theme={null}
curl --request POST \
--url https://staging.crossmint.com/api/2022-06-09/collections/{collectionId}/nfts \
--header 'Content-Type: application/json' \
--header 'X-API-KEY: ' \
--data '{
"recipient": "polygon-amoy:0x123...",
"contractArguments": {
"tokenId": "42"
}
}'
```
## Best Practices
When using your own smart contract with Crossmint:
1. **Test Thoroughly**: Test your contract integration in the staging environment before moving to production.
2. **Function Visibility**: Ensure your minting function has the appropriate visibility and access controls.
3. **Gas Optimization**: Optimize your contract to minimize gas costs for minting operations.
4. **Error Handling**: Implement proper error handling in your contract to provide meaningful error messages.
## FAQs
Yes, you can use an existing deployed contract as long as it meets the prerequisites mentioned above.
Your contract must have a function that allows Crossmint to mint NFTs without paying any fees. If your contract
doesn't have this, you'll need to modify it or deploy a new version.
Yes, you can still use Crossmint's email delivery system with your own contract. Just specify an email recipient
in the mint request.
Token ID management depends on your contract implementation. You can either pass a specific token ID as a
parameter or let your contract handle token ID assignment internally.
# Configure Admin Functions
Source: https://docs.crossmint.com/minting/nfts/integrate/configure-admin-functions
Manage token controls including admin burn and admin transfer
Crossmint provides admin functions that allow you to control your NFTs throughout their lifecycle.
## Admin Transfer
The NFT Admin Transfer API allows collection owners to transfer NFTs between user wallets.
Admin transfers only work for Solana cNFTs. [Contact us](https://www.crossmint.com/contact/sales) if you want to
access to this API.
## Admin Burn
The [Burn NFT API](/api-reference/minting/nfts/burn-nft) allows collection owners to permanently remove NFTs from circulation.
Once an NFT is burned, this action cannot be reversed.
## Requirements
To use admin functions:
1. Your collection must be of a supported type (Solana cNFTs for transfers, Solana cNFTs or EVM for burns)
2. Your API key must have the appropriate scopes (`nfts.transfer` for transfers, `nfts.delete` for burns)
3. You must be the collection owner or have admin privileges
# Create Collections
Source: https://docs.crossmint.com/minting/nfts/integrate/create-collections
Deploy smart contracts and NFT collections
A collection is a container of NFTs, used by applications like marketplaces and wallets to group NFTs together.
Crossmint allows you to create managed collections via API or directly from the console. Crossmint has a library of pre-audited smart contracts which work for most major use cases. However, you can also [bring your own contract](/minting/nfts/integrate/bring-your-own-contract) if you already have one.
Crossmint supports non-fungible and semi-fungible tokens (editions), free and paid mints, and builds on open ERC and Metaplex standards. On EVM chains, ERC-721 and ERC-1155 contracts are supported, while on Solana, Metaplex standard programs and compressed NFT programs are supported.
See the list of supported blockchains [here](/introduction/supported-chains)
## 1. Create and Deploy an NFT collection
The first time you mint an NFT on a specific blockchain, Crossmint will assign it, and any subsequent mints, to a default collection for that chain. You can create additional collections from the console or in a single API call (requires the API key scope `collections.create`):
```bash cURL theme={null}
curl --request POST \
--url https://staging.crossmint.com/api/2022-06-09/collections/ \
--header 'content-type: application/json' \
--header 'x-api-key: ' \
--data '
{
"chain": "polygon",
"metadata": {
"name": "A new collection",
"imageUrl": "https://www.crossmint.com/assets/crossmint/logo.png",
"description": "A new collection with its own dedicated smart contract"
}
}
'
```
The collection details you provide will be displayed to your customers on marketplaces and other interfaces.
If you intend to sell the NFTs in your collection, read the guide on [how to enable
payments](/minting/nfts/integrate/list-for-sale) first.
## 2. Check the status of your collection
It takes a few seconds (up to a minute, depending on the blockchain and how congested it is) to deploy a collection.
You can use the following API to check what the status of a collection is. For example:
```bash cURL theme={null}
# Set your variables
API_KEY=""
ENV="staging" # or "www" for production
ACTION_ID=""
curl --request GET \
--url "https://${ENV}.crossmint.com/api/2022-06-09/actions/${ACTION_ID}" \
--header "X-API-KEY: ${API_KEY}"
```
```javascript checkStatus.js theme={null}
const apiKey = "";
const env = "staging"; // or "www" for production
const actionId = "";
const url = `https://${env}.crossmint.com/api/2022-06-09/actions/${actionId}`;
const options = {
method: "GET",
headers: { "X-API-KEY": apiKey },
};
fetch(url, options)
.then((response) => response.json())
.then((response) => console.log(response))
.catch((err) => console.error(err));
```
## 3. List all collections under your project
```bash cURL theme={null}
curl --request GET \
--url https://staging.crossmint.com/api/2022-06-09/collections/ \
--header 'x-client-secret: ' \
--header 'x-project-id: '
```
Test any API in seconds directly from the docs.
Contact our sales team for support.
# Define Metadata
Source: https://docs.crossmint.com/minting/nfts/integrate/define-metadata
Understand NFT metadata standards & storage options
Metadata in the context of NFTs includes essential information such as the name, description, and media (e.g., images, videos) associated with the asset.
### Metadata Standards
Collection and NFT metadata (e.g. title, description, image) must be specified in JSON format and follow industry conventions to ensure it renders appropriately across wallets and marketplaces.
Crossmint supports rich metadata like audio, video, and HTML. To include rich metadata, simply populate `animation_url` as specified in the links below.
Crossmint's standard plan has a **10mb** file size limit. For larger uploads, you must upgrade to a [premium
plan](https://www.crossmint.com/contact/sales) or handle the upload yourself.
Metadata standards differ slightly across blockchains:
EVM chains follow the OpenSea standard. Refer to their official documentation to see what attributes can be included and how. Note that `name` and `image` are mandatory fields.
```json EVM Metadata Example theme={null}
{
"name": "Crossmint Test NFT",
"description": "Created with the Crossmint minting API",
"image": "https://bafkreiexjl6kw4khdxkrt6dojgacscnzvrys47t472l2t7d6r2ss65kifq.ipfs.nftstorage.link/",
"external_url": "https://docs.crossmint.com",
"attributes": [
{
"trait_type": "background",
"value": "black"
},
{
"trait_type": "flavor",
"value": "minty"
}
]
}
```
Solana follows the Metaplex Metadata Standard. For more information, refer to the official Metaplex documentation - see the JSON structure section.
```json Solana Metadata Example theme={null}
{
"name": "Crossmint Test NFT",
"symbol": "XMINT",
"description": "Created with the Crossmint minting API",
"seller_fee_basis_points": 5000,
"image": "https://bafkreiexjl6kw4khdxkrt6dojgacscnzvrys47t472l2t7d6r2ss65kifq.ipfs.nftstorage.link/",
"attributes": [
{
"trait_type": "background",
"value": "black"
},
{
"trait_type": "flavor",
"value": "minty"
}
],
"properties": {
"files": [
{
"uri": "https://bafkreiexjl6kw4khdxkrt6dojgacscnzvrys47t472l2t7d6r2ss65kifq.ipfs.nftstorage.link/",
"type": "image/png"
}
],
"category": "image",
"creators": [
{
"address": "xm2Mc3HEd2bGJXeqJEBrek4bj79gxzYkBaEobnpFraH",
"share": 100
}
]
}
}
```
Aptos follows the Aptos Legacy Token Standard. For more information, refer to the official Aptos Documentation - see the JSON structure section.
```json Aptos Metadata Example theme={null}
{
"name": "Crossmint Test NFT",
"description": "Aptos Attributes Test",
"image": "https://www.arweave.net/abcd5678?ext=png",
"animation_url": "https://www.arweave.net/efgh1234?ext=mp4",
"external_url": "https://petra.app/",
"attributes": [
{
"trait_type": "web",
"value": "yes"
},
{
"trait_type": "mobile",
"value": "yes"
},
{
"trait_type": "extension",
"value": "yes"
}
],
"properties": {
"files": [
{
"uri": "https://www.arweave.net/abcd5678?ext=png",
"type": "image/png"
},
{
"uri": "https://watch.videodelivery.net/9876jkl",
"type": "unknown",
"cdn": true
},
{
"uri": "https://www.arweave.net/efgh1234?ext=mp4",
"type": "video/mp4"
}
],
"category": "video"
}
}
```
## Crossmint Metadata Storage
Metadata must be stored somewhere to be accessible and verifiable on the blockchain. Crossmint automatically uploads and validates metadata for you on decentralized storage solutions, ensuring a seamless minting experience.
### Supported Storage Providers
Crossmint supports two primary storage providers:
* **IPFS (InterPlanetary File System)** (Default): A widely adopted decentralized storage standard ensuring compatibility across blockchain ecosystems by enabling peer-to-peer file storage and retrieval.
* **Walrus Network**: A new decentralized storage solution designed for efficient and scalable metadata hosting. Walrus is currently not supported for EVM NFT templates.
If you are interested in using **Arweave** for metadata storage, please contact us for further information.
### Configuring Storage
By default, Crossmint uses IPFS to store metadata and media. However, if you prefer to use Walrus, you can configure this in the [Crossmint Console](https://staging.crossmint.com/console) under **Settings > General**.
If you prefer to upload and manage media files independently, you can pass a direct URL to the [Mint API](/api-reference/minting/nfts/mint-nft)
and set the `reuploadLinkedFiles` flag to `false`. This prevents Crossmint from automatically reuploading media files
to decentralized storage.
***
## FAQs
You may store any text and image file. Additionally, you may include multi-media files such as GLTF, GLB, WEBM, MP4, M4V, OGV, and OGG, along with the audio-only extensions MP3, WAV, and OGA. Please stored them as `animation_url`.
In addition, `animation_url` also supports HTML pages, allowing you to build rich experiences and interactive NFTs using JavaScript canvas, WebGL, and more. Scripts and relative paths within the HTML page are also supported. However, access to browser extensions is not supported.
The maximum file size is 10mb. For larger files, you must upgrade to an enterprise plan or upload the metadata yourself and pass the URI to the API.
Yes. You must handle the upload yourself and pass the URL to the API.
# List for Sale
Source: https://docs.crossmint.com/minting/nfts/integrate/list-for-sale
Configure royalties and marketplace profiles for your NFT collections
When selling NFTs on secondary markets, you can configure royalties to receive a percentage of each sale and optimize your collection's marketplace profile to attract buyers.
## Sell Tokens
The [Mint Tokens guide](/minting/nfts/integrate/mint-tokens) showed you how to create different types of non-fungible assets.
In this guide you will learn how to enable Crossmint's Checkout so you
can sell them to your customers using cryptocurrency or credit card.
### How It Works
* You can set a price in crypto (ETH, MATIC, SOL or USDC).
* For imported and secondary collections, payouts arrive instantly. For managed collections, payouts arrive after a 24-hour withholding period.
* Your customers can pay with crypto or credit card.
* You pay credits for NFT gas, user pays only for the item.
### Configure a Collection for Sale
1. [Create an NFT collection](/minting/nfts/integrate/create-collections) and navigate
to it in the console.
2. Upload NFT "templates" for all the NFTs you wish to list for sale. You
can do so from the "NFTs" tab in the console, or by using
[the API](/api-reference/minting/template/create-template).
3. Navigate to the `Checkout` entry on the navbar in the collections page.
Follow the wizard to enable payments.
4. (Production only) [Verify your account and collection](/introduction/platform/account-verification). Not required in staging.
5. Share the url or QR code with your users.
**Enable at collection creation time**
Pass a `payments` object to the [Collection Creation
API](/api-reference/minting/collection/create-collection) with the
`price`,
`recipientAddress` and `currency`. You can optionally set up a
`supplyLimit` to cap the collection supply.
The following snippet creates a new sales-enabled collection:
```bash cURL theme={null}
curl --request POST \
--url https://staging.crossmint.com/api/2022-06-09/collections/ \
--header 'Content-Type: application/json' \
--header 'X-API-KEY: ' \
--data '{
"chain": "polygon",
"fungibility": "non-fungible",
"metadata": {
"description": "This is a sample NFT collection",
"imageUrl": "https://www.crossmint.com/assets/crossmint/logo.png",
"name": "Sample NFT Collection",
},
"payments": {
"price": "",
"recipientAddress": "",
"currency": "usdc"
},
"supplyLimit": 123
}'
```
**Enable on an existing collection**
If you had already created a collection, use the [Update Collection API](/api-reference/minting/collection/update-collection) to enable sales on it.
Enabling sales in production requires [verifying your account](/introduction/platform/account-verification).
***
### Set Up a Checkout
Now that your collection accepts payments, the next step is to integrate one of
Crossmint's [Checkout](/payments/introduction) variants into your app
or website, and start accepting sales.
There are two ways to integrate:
Add a button to your site which opens a checkout in a pop-up or new tab.
Insert a checkout inside your site for maximum control over the user experience.
## Set Collection Royalties
With royalties, you can receive revenue when your NFTs are traded on secondary markets. Crossmint allows you to configure royalties for a collection via API and (soon) directly from the console. You just have to specify the % of royalties and where you want those funds to be sent.
Royalties are defined at the collection level, not at the NFT level. Updating the royalties of a collection will update all NFTs, including the ones which have already been minted.
This API follows royalty standards compatible with virtually all marketplaces. However, note that some marketplaces have determined to not honor royalties, so transactions occuring there may not generate additional revenue.
Available self-serve for ERC-721 collections in EVM chains. Please [contact
us](https://www.crossmint.com/contact/sales) for ERC-1155 or Solana support.
| Endpoint | Description | |
| :--------------------------------------------------------------------------------------- | :---------------------- | :- |
| [Add / Edit Royalties](/api-reference/minting/collection/set-royalties) | Add or edit royalties | |
| [Get Royalty Information](/minting/nfts/integrate/list-for-sale#get-royalty-information) | Get royalty information | |
| [Disable Royalties](/minting/nfts/integrate/list-for-sale#disable-royalties) | Disable royalties | |
### Add / Edit Royalties
To add or edit royalties for a collection, use the following API endpoint:
```bash cURL theme={null}
curl --request POST \
--url https://staging.crossmint.com/api/v1-alpha1/minting/collections/{collectionId}/royalties \
--header 'Content-Type: application/json' \
--header 'X-API-KEY: ' \
--data '{
"recipients": [
{
"address": "0x123...",
"basisPoints": 5
}
]
}'
```
### Get Royalty Information
To retrieve the current royalty configuration for a collection:
```bash cURL theme={null}
curl --request GET \
--url https://staging.crossmint.com/api/v1-alpha1/minting/collections/{collectionId}/royalties \
--header 'X-API-KEY: '
```
### Disable Royalties
To disable royalties for a collection:
```bash cURL theme={null}
curl --request DELETE \
--url https://staging.crossmint.com/api/v1-alpha1/minting/collections/{collectionId}/royalties \
--header 'X-API-KEY: '
```
## Implementation Examples
```javascript theme={null}
// Example: Setting up royalties for a new collection
const apiKey = "YOUR_API_KEY";
const collectionId = "your-collection-id";
const env = "staging"; // or "www" for production
// Define royalty recipients
const royaltyRecipients = [
{
address: "0x123abc...", // Creator wallet
basisPoints: 5 // 5% royalty
},
{
address: "0x456def...", // Community fund wallet
basisPoints: 2.5 // 2.5% royalty
}
];
// Set up royalties
const url = `https://${env}.crossmint.com/api/v1-alpha1/minting/collections/${collectionId}/royalties`;
const options = {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-API-KEY": apiKey
},
body: JSON.stringify({
recipients: royaltyRecipients
})
};
fetch(url, options)
.then(response => response.json())
.then(data => console.log("Royalties configured:", data))
.catch(error => console.error("Error configuring royalties:", error));
```
## Marketplace Profile
Optimizing your collection's profile on marketplaces like OpenSea, Rarible, and others can significantly impact your NFT sales and visibility.
At this time, updating your collection profile on OpenSea, Rarible, and other marketplaces requires manual work and is limited to enterprise clients.
### Key Marketplace Profile Elements
When working with our enterprise team to configure your marketplace profile, consider these important elements:
1. **Collection Banner**: A high-quality banner image that represents your brand
2. **Collection Logo**: A distinctive logo that's recognizable at small sizes
3. **Collection Description**: A compelling description of your project and its value
4. **External Links**: Links to your website, social media, and community channels
5. **Category Tags**: Appropriate categories that help collectors discover your NFTs
## FAQs
Royalty enforcement varies by marketplace. Some marketplaces like OpenSea have made royalties optional, while
others still enforce them. Crossmint implements royalties according to blockchain standards, but cannot
guarantee enforcement across all marketplaces.
No, royalties are set at the collection level and apply to all NFTs within that collection. If you need
different royalty structures, consider creating separate collections.
Royalty changes take effect immediately for new listings. For existing listings on marketplaces, the timing
depends on how frequently each marketplace refreshes metadata.
While technically there's no upper limit, most marketplaces and collectors expect royalties between 2.5% and
10%. Setting royalties too high may discourage secondary market activity.
# Manage Delivery
Source: https://docs.crossmint.com/minting/nfts/integrate/manage-delivery
Configure recipient options and customize email delivery notifications
Crossmint provides flexible options for delivering NFTs to recipients and customizing the delivery experience. This guide covers how to specify different types of recipients and how to customize the email notifications they receive.
## Specify Recipients
You can deliver NFTs to three kinds of recipients when using minting APIs:
* [Wallet address](#send-nft-to-wallet-address): directly specify a wallet address, which could be a user owned wallet (e.g. MetaMask, Phantom, etc.) or a wallet you manage.
* [Email address](#send-nft-to-email-address): in this modality, Crossmint automatically generates a secure MPC backed custodial wallet for the email, and delivers the NFT inside.
* [User identifier](#send-nft-by-userid): in this modality, Crossmint automatically generates a wallet and associates it with an user ID from your application, and delivers the NFT inside. You must build the interface for users to access this wallet.
Check out the [create-wallet](/api-reference/wallets/create-wallet) endpoint in the API reference if you wish to
pre-create a wallet prior to invoking the mint API.
### Send NFT to Wallet Address
To mint an NFT directly to an existing blockchain address, the following `recipient` format is used:
```bash theme={null}
:
```
For a full list of chain names, refer to the [Supported Chains](/introduction/supported-chains) page.
#### Examples
* EVM: `polygon:0x123...`
* Solana: `solana:3Q5...`
* Aptos: `aptos:0x0f07...`
### Send NFT to Email Address
To mint an NFT and send it to an email address, the following `recipient` format is used:
```bash theme={null}
email::
```
#### Examples
* EVM: `email:demo@test.com:polygon`
* Solana: `email:demo@test.com:solana`
Minting to an email address is not currently supported on Aptos.
The NFT can then be accessed by logging into Crossmint with the specified email address:
* for `staging/testnet` NFTs: [https://staging.crossmint.com/user/collection](https://staging.crossmint.com/user/collection)
* for `mainnet` NFTs: [https://www.crossmint.com/user/collection](https://www.crossmint.com/user/collection)
### Send NFT to a Twitter/X account
To mint an NFT to a Twitter/X account, the following `recipient` format is used:
```bash theme={null}
twitter::
```
#### Examples
* EVM: `twitter:@username:polygon`
* Solana: `twitter:@username:solana`
### Send NFT by `userId`
This method allows you to deliver NFTs by directly specifying the user identifier of the recipient in your system. Crossmint will fetch a custodial wallet linked to that user identifier inside your project or, if none exists, create one on the fly. Then the NFT will be delivered there. This way, you don't need to keep a mapping between your user identifiers and their wallets, just pass your user id and crossmint takes care of the rest.
Wallets created with the `userId` option cannot be accessed by logging into Crossmint.com.
To mint an NFT to this type of recipient, follow this format:
```bash theme={null}
userId::
```
#### Examples
* EVM: `userId:user1234:polygon`
* Solana: `userId:user1234:solana`
## Customize Delivery Emails
Email delivery notifications can be customized to align with your branding and communication needs. This section will walk you through how to configure your email template.
### Edit the email template
To adjust the visual presentation and branding of your email notifications:
1. In the Crossmint Console, click on Settings, and navigate to the **Branding** tab.
2. Select **Deliver NFT via email** from the dropdown options.
3. Here, you can customize:
* The **logo** displayed in the email with your logo. This will default to Crossmint's logo, unless specified otherwise.
* The main **button's color** . This will default to #04AA6D (Crossmint's green).
* The **display name** textbox to include your brand's name. This will be empty, by default.
As you make adjustments, you can preview the changes. Once satisfied, save your changes and now they will apply to all future minting email notifications, related to this project. Emails now reflect your project's brand identity while providing essential delivery information to recipients.
### Enable email delivery
You should make sure email delivery notifications are enabled for the customized email to reach your users. For new projects created after Sep 16, 2024, the "sendNotification" parameter is set to `true` by default, meaning mint recipients will automatically receive email notifications.
For legacy projects, created before Sep 16 2024, "sendNotification" is set to `false` until March 14, 2025. If you choose so, you can enable email notifications for legacy projects by explicitly turning the feature on via API or via the Console.
#### Console
To send email notifications to NFT recipients:
1. Navigate to an **unminted NFT** part of a Collection in the Crossmint Console.
2. Click on the **more options** button and select **Mint & send NFT**.
3. Ensure the **Notify recipient via email** option is selected.
4. Enter the **recipient's email address** and press Mint.
The recipient will receive an email delivery notification for their newly minted NFT.
#### API
To do the same programmatically, you can configure two new parameters:
1. **sendNotification** (boolean): Determines whether the recipient will receive an email notification.
* Default value: `true` for new projects created after Sep 16, 2024.
* For legacy projects (created before Sep 16, 2024), this feature will default to `false` until March 14, 2025, unless explicitly set to `true`.
2. **locale** (string): Specifies the language for the email content. Default is `en-US` for English. We support all locales (i.e. "es-ES" for Spanish).
## FAQs
Yes, recipients can manage their email preferences through their Crossmint account settings.
If an email address is invalid, the NFT will still be minted but the notification will fail to deliver. The NFT
will be associated with the email address in Crossmint's system, but the recipient won't receive a notification.
Currently, each API call mints to a single recipient. For bulk operations, you'll need to make multiple API
calls. See the [Mint In Bulk](/minting/nfts/integrate/mint-in-bulk) guide for more information.
Crossmint doesn't currently provide delivery tracking for email notifications. For critical notifications,
consider implementing your own email delivery system alongside Crossmint's.
# Mint In Bulk
Source: https://docs.crossmint.com/minting/nfts/integrate/mint-in-bulk
Scale your NFT operations with bulk minting capabilities
Crossmint's infrastructure is designed to handle high-volume minting operations efficiently, allowing you to scale your NFT projects to serve thousands or even millions of users.
## Enterprise Solutions
For enterprise customers with very high volume requirements, Crossmint offers additional capabilities:
Enterprise bulk minting features are available for high-volume projects. Please [contact
us](https://www.crossmint.com/contact/sales) to discuss your specific requirements.
## Bulk Minting Capabilities
Crossmint's platform offers several advantages for bulk minting operations:
* **High Throughput**: Mint thousands of NFTs per hour with optimized transaction batching
* **Cost Efficiency**: Reduce gas costs through intelligent transaction scheduling
* **Queue Management**: Automatic handling of transaction queues and retries
* **Observability**: Track the status of all minting operations in real-time
* **Gas Optimization**: Automatic gas price adjustments based on network conditions
## Bulk Minting via Crossmint Console
The Crossmint Console provides an easy-to-use interface for uploading and minting NFTs in bulk. Here's how to use it:
### Step 1: Access the Batch Upload Feature
In the Crossmint Console, navigate to your collection and select the "Batch upload" option.
### Step 2: Prepare Your Metadata
Before uploading your assets, you need to prepare them properly:
1. You can upload up to 1,000 NFT collectibles at once
2. Download the example CSV file provided by Crossmint to use as a template
3. Prepare your metadata according to the template format
### Step 3: Upload Your Metadata CSV
1. Add a CSV file named `metadata.csv` with your NFT metadata
2. Click the "Upload CSV" button to upload your prepared file
### Step 4: Upload Media Files
1. Prepare your NFT media files (images, animations, videos)
2. Crossmint supports various formats (PNG, JPEG, GIF etc.)
3. Each file must be under 10MB in size
4. All media files should be in the same folder, not in subfolders
5. Click "Upload media files" to upload your prepared media
### Step 5: Complete the Upload
After uploading both your metadata CSV and media files, click the "Upload" button to start the batch minting process.
## Monitoring Bulk Operations
When minting in bulk, it's essential to track the status of your operations:
1. **Webhooks**: Set up [webhooks](/minting/nfts/integrate/webhooks-and-status-apis) to receive real-time notifications about mint completions and failures
2. **Status API**: Use the [action status API](/minting/nfts/integrate/webhooks-and-status-apis) to check the status of individual minting operations
3. **Console Dashboard**: Monitor your minting operations through the Crossmint Console
# Mint Tokens
Source: https://docs.crossmint.com/minting/nfts/integrate/mint-tokens
Create and distribute tokens at scale
To mint and airdrop unique digital assets, you can follow the guide on the [Quickstart](/minting/quickstarts/nfts). For more detail, please check the [API reference](/api-reference/minting/nfts/mint-nft). You can watch a quick video tutorial for this [here](https://youtu.be/yYJRW35zqUQ?si=b6PQTbZ7zqYVGwXw).
SFTs (semifungible tokens) follow the ERC-1155 standard. Each token is a replica of a predefined template. Each collection (smart contract) can contain multiple templates, which can contain many tokens.
To get started, it's recommended to read the [Introduction](/minting/introduction) for general information on how the minting product works.
This API only supports EVM chains self-serve. Contact us if you need support for another chain.
## 1. Create an SFT collection to hold your templates
```bash cURL theme={null}
curl --request POST \
--url https://staging.crossmint.com/api/2022-06-09/collections \
--header 'accept: application/json' \
--header 'content-type: application/json' \
--header 'x-api-key: YOUR_API_KEY' \
--data '
{
"chain": "polygon-amoy",
"fungibility": "semi-fungible",
"metadata": {
"name": "My New Collection",
"imageUrl": "https://www.crossmint.com/assets/crossmint/logo.png",
"description": "A new collection with its own dedicated smart contract"
}
}'
```
```json JSON theme={null}
{
"id": "5263650e-6d43-4ed3-9e31-0cf593d076a4",
"metadata": {
"name": "Test Collection",
"description": "Test",
"imageUrl": "https://cdn.io/metadata.json",
"symbol": "XMINT"
},
"fungibility": "semi-fungible",
"onChain": {
"chain": "polygon-amoy",
"type": "erc-1155"
},
"actionId": "5263650e-6d43-4ed3-9e31-0cf593d076a4"
}
```
## 2. Create a template within that collection
```bash cURL theme={null}
curl --request POST \
--url https://staging.crossmint.com/api/2022-06-09/collections/{collectionId}/templates \
--header 'accept: application/json' \
--header 'content-type: application/json' \
--header 'x-api-key: YOUR_API_KEY' \
--data '
{
"onChain": {
"tokenId": "2"
},
"supply": {
"limit": 10
},
"metadata": {
"name": "My template",
"image": "https://www.crossmint.com/assets/crossmint/logo.png",
"description": "A new token template for my ERC1155 collection"
}
}'
```
```json JSON theme={null}
{
"templateId": "58b0c1aa-e457-48dd-bb55-5a27e6a92f74",
"metadata": {
"name": "My template",
"image": "ipfs://bafkreigbqsmxzkbjgbwtj6exfdt5z3t3swgoysf7hr6vjzddqnmykj6x2u",
"description": "A new token template for my ERC1155 collection"
},
"onChain": {
"tokenId": "1"
},
"supply": {
"limit": "10",
"minted": "0"
}
}
```
## 3. Mint an SFT from a template
Mint SFTs from the template and send them to wallets or email addresses.
```bash cURL theme={null}
curl --request POST \
--url https://staging.crossmint.com/api/2022-06-09/collections/{collectionId}/sfts \
--header 'accept: application/json' \
--header 'content-type: application/json' \
--header 'x-api-key: YOUR_API_KEY' \
--data '
{
"templateId": "58b0c1aa-e457-48dd-bb55-5a27e6a92f74",
"recipient": "email:user@example.com:polygon-amoy",
"amount": 1
}'
```
```json JSON theme={null}
{
"actionId": "a91c15e3-60f2-4a45-bf1a-cee508981667",
"action": "nfts.create",
"status": "pending",
"data":
{
"chain": "polygon-amoy",
"collection":
{
"id": "84e3d617-9c1b-4e7a-9686-522a9ea7c520",
"contractAddress": "0x9b8ab8949bd7E73E61945b88F7fe12151f98ad3C"
},
"recipient":
{
"walletAddress": "0xcFDc00Cf926A5053f9Cdf004e6DF17e6dEB2E146",
"email": "testy@crossmint.com"
},
"token":
{
"id": "a91c15e3-60f2-4a45-bf1a-cee508981667"
}
},
"startedAt": "2024-01-02T22:05:01.000Z",
"resource": "https://staging.crossmint.com/api/2022-06-09/actions/a91c15e3-60f2-4a45-bf1a-cee508981667"
}
```
**All set!**
To confirm delivery, use the [transaction status API](/minting/nfts/integrate/webhooks-and-status-apis) or set up a [webhook](/minting/nfts/integrate/webhooks-and-status-apis).
## What are Compressed NFTs?
Compressed NFTs are a new standard on the Solana blockchain, for minting NFTs with the lowest cost amongst L1 and L2 blockchains, and the highest throughput (thousands of NFTs per second). Read more details about how it works [here](https://www.metaplex.com/posts/expanding-digital-assets-with-compression-for-nfts).
Using this standard to mint NFTs is extremely complicated if you manage everything on your own. On top of the intrinsic complexity usually required to mint a regular NFT, compressed NFTs require deploying and managing Merkle trees, batching, and dealing with nascent infrastructure. Crossmint also supports updating the NFT metadata after minting. Please refer to this [doc page](/minting/nfts/integrate/update-nfts).
Here's where Crossmint can help: we've done all the hard work so that minting compressed NFTs is no harder than minting regular ones: all it takes is a single API call, and you can even use one of Crossmint's no-code tools to do so.
## Current Protocol Limitations
* There's a 10-30 second delay between when an NFT is minted and it shows in wallets.
## How to use the Minting API with Compressed NFTs
The Minting API for Compressed NFTs is exactly the same as for regular NFTs, but it only works on the Solana blockchain.
POST `https://staging.crossmint.com/api/2022-06-09/collections//nfts`
You can mint both compressed and non-compressed NFTs on the same collection.
```bash cURL theme={null}
curl --request POST \
--url https://staging.crossmint.com/api/2022-06-09/collections/default-solana/nfts \
--header 'Content-Type: application/json' \
--header 'X-API-KEY: ' \
--data '{
"recipient": "email:john.doe@example.com:solana",
"metadata": {
"name": "Crossmint Example NFT",
"image": "https://www.crossmint.com/assets/crossmint/logo.png",
"description": "My NFT created via the mint API!"
},
"compressed": true,
"reuploadLinkedFiles": false
}'
```
The above will return an NFT ID. Use it in the next call to retrieve the mint status.
```bash cURL theme={null}
curl --request GET \
--url https://staging.crossmint.com/api/2022-06-09/collections/{collectionId}/nfts/{nftId} \
--header 'X-API-KEY: X_API_KEY'
```
Be sure to replace:
* `X_API_KEY`, `{collectionId}` and `{nftId}`
* Recipient: change `VALID_WALLET_ADDRESS` with the wallet you want to send the NFT to
* Metadata: add the name, image, and description.
Other explorers, like Solscan, still have not added support.
There are two ways to set the transferrability of NFTs:
1. Defining it at the smart contract level
2. If you are using custodial wallets, enabling or disabling transfers from the frontend
If you want to configure it at the smart contract level, see the following endpoints:
| Endpoint | Chain | Description |
| ---------------------------------------------------------------------------- | ------------- | --------------------------------- |
| [Set Transferability](/api-reference/minting/collection/set-transferability) | EVM and Aptos | Set transferability to on or off |
| [Get Transferability](/api-reference/minting/collection/get-transferability) | EVM and Aptos | Get transferability configuration |
For Solana or other non-EVM chain support, [contact us](https://www.crossmint.com/contact/sales)
***
The API also has an idempotent version which prevents you from calling the same API action multiple times and
mitigates the risk of unwanted NFT duplicates.
## Advanced Guides
# Pricing
Source: https://docs.crossmint.com/minting/nfts/integrate/pricing
Crossmint Mint API Pricing
## Chain operations pricing
*All values shown in USD.*
| Chain | Create Collection | Update Collection | Mint NFTs | Update Royalty | Update Metadata |
| :-------------- | ----------------: | ----------------: | -----------: | -------------: | --------------: |
| Aptos | \$0.10 | Not supported | \$0.01 | Not supported | Not supported |
| Arbitrum\* | \$0.03 + gas | \$0.03 + gas | \$0.03 + gas | \$0.03 + gas | \$0.03 + gas |
| Arbitrum Nova\* | \$0.01 + gas | \$0.01 + gas | \$0.01 + gas | \$0.01 + gas | \$0.01 + gas |
| Avalanche\* | \$0.10 + gas | \$0.10 + gas | \$0.10 + gas | \$0.10 + gas | \$0.10 + gas |
| Base\* | \$0.08 + gas | \$0.08 + gas | \$0.08 + gas | \$0.08 + gas | \$0.08 + gas |
| BSC\* | \$0.01 + gas | \$0.01 + gas | \$0.01 + gas | \$0.01 + gas | \$0.01 + gas |
| Chiliz | \$1 | \$0.20 | \$0.50 | \$0.20 | \$0.20 |
| Mode | \$0.05 | \$0.05 | \$0.05 | \$0.05 | \$0.05 |
| Optimism\* | \$0.06 + gas | \$0.06 + gas | \$0.06 + gas | \$0.06 + gas | \$0.06 + gas |
| Polygon | \$0.50 | \$0.50 | \$0.10 | \$0.50 | \$0.05 |
| Rari | \$0.20 | \$0.20 | \$0.20 | \$0.20 | \$0.20 |
| Sei | \$0.05 | \$0.01 | \$0.01 | \$0.01 | \$0.01 |
| Shape | \$2 | \$2 | \$2 | \$2 | \$2 |
| Solana | \$8 | Not supported | \$0.02 | Not supported | \$0.02 |
| Skale Nebula | \$0.01 | \$0.01 | \$0.01 | \$0.01 | \$0.01 |
| Viction | \$0.01 | \$0.01 | \$0.01 | \$0.01 | \$0.01 |
| Xai | \$0.01 | \$0.01 | \$0.01 | \$0.01 | \$0.01 |
| Zora\* | \$0.08 + gas | \$0.08 + gas | \$0.08 + gas | \$0.08 + gas | \$0.08 + gas |
\*For some chains (Arbitrum, Arbitrum Nova, Avalanche, Base, BSC, Optimism, and Zora), gas fees are added to the base price. Gas fees are determined at the time the operation is submitted and depend on blockchain network congestion.
For more details, or to discuss custom pricing for large operations volumes, [contact our sales team](https://www.crossmint.com/contact/sales).
# Update NFTs
Source: https://docs.crossmint.com/minting/nfts/integrate/update-nfts
Make your NFTs change over time
Dynamic NFTs are tokens whose content changes over time. There are two ways to achieve this with Crossmint:
1. Using Crossmint's [Edit NFT API](/api-reference/minting/nfts/edit-nft)
2. Storing the metadata offchain and updating it on your database. To use this option, set the property `reuploadLinkedFiles` to `false` when minting an NFT.
## Using the Edit NFT API
The [Edit NFT API](/api-reference/minting/nfts/edit-nft) allows you to update the metadata of an existing NFT, including its name, description, image, and attributes. This is useful for creating NFTs that evolve over time or need to be updated based on external events.
### Prerequisites
* Ensure your API key is a server-side key with the `nfts.update` scope
* You need the collection ID and NFT ID from the minting process
### Implementation
```bash cURL theme={null}
curl --request PATCH \
--url https://staging.crossmint.com/api/2022-06-09/collections/{collectionId}/nfts/{nftId} \
--header 'Content-Type: application/json' \
--header 'X-API-KEY: ' \
--data '{
"metadata": {
"description": "My updated NFT description!",
"image": "https://www.crossmint.com/assets/crossmint/updated-image.png",
"name": "Evolved NFT",
"attributes": [
{
"trait_type": "level",
"value": "2"
}
]
},
"reuploadLinkedFiles": true
}'
```
## Using Off-chain Metadata
You can also store the metadata off-chain and update it directly:
1. When minting the NFT, set `reuploadLinkedFiles` to `false`
2. Use your own server to host the metadata
3. Update the metadata on your server as needed
```javascript theme={null}
// Example: Minting with off-chain metadata
const apiKey = "YOUR_API_KEY";
const chain = "polygon-amoy"; // or "ethereum-sepolia", "base-sepolia", etc.
const env = "staging"; // or "www" for production
const recipientEmail = "TEST_EMAIL_ADDRESS";
const recipientAddress = `email:${recipientEmail}:${chain}`;
// The URL to your metadata server
const metadataImgUrl = "https://your-server.com/metadata/token123.png";
const url = `https://${env}.crossmint.com/api/2022-06-09/collections/default/nfts`;
const options = {
method: "POST",
headers: {
accept: "application/json",
"content-type": "application/json",
"x-api-key": apiKey,
},
body: JSON.stringify({
recipient: recipientAddress,
metadata: {
name: "Dynamic NFT",
description: "This NFT will update over time",
image: metadataImgUrl,
},
reuploadLinkedFiles: false,
}),
};
fetch(url, options)
.then((response) => response.json())
.then((data) => console.log(data))
.catch((error) => console.error("Error:", error));
```
## Use Cases for Dynamic NFTs
Dynamic NFTs enable a wide range of applications:
* **Game Assets**: NFTs that evolve as players progress
* **Membership Passes**: NFTs that update based on membership status
* **Event Tickets**: NFTs that change before, during, and after events
* **Digital Collectibles**: NFTs that evolve based on real-world events
* **Loyalty Programs**: NFTs that update based on customer activity
## Best Practices
When implementing dynamic NFTs:
1. **Plan for Updates**: Design your NFT with future updates in mind
2. **Versioning**: Consider including version information in your metadata
3. **Event Triggers**: Define clear conditions for when updates should occur
4. **User Communication**: Inform users about how and when their NFTs might change
5. **Testing**: Thoroughly test update mechanisms before implementation
## FAQs
With Crossmint's Edit API, you can update NFTs as needed, but consider blockchain transaction costs and network
congestion. For very frequent updates, consider using off-chain metadata.
Updates don't change the token ID or ownership history, but the perceived value may change based on the nature
of the updates. It's important to communicate your update policy to collectors.
Crossmint doesn't provide native scheduling, but you can implement scheduled updates using your own backend
services that call the Edit API at specified times.
Most marketplaces will display the current state of your NFT's metadata, but the frequency of metadata refreshes
may vary by marketplace.
# Webhooks and Status APIs
Source: https://docs.crossmint.com/minting/nfts/integrate/webhooks-and-status-apis
Listen for updates in mints, edits, collection creations and other async events
Creating NFT collections and minting or [editing NFTs](/minting/nfts/integrate/update-nfts) are operations that must be sent to a blockchain. Transaction confirmation on the blockchain can take a few seconds, but during network congestion, it may take several minutes. Webhooks and the action status API allow you to stay up to date on the status of these asynchronous operations.
Some cases where you may want to listen to when transactions are confirmed include:
* Notifying your customers via email that their NFT is ready to access
* Updating your database with the NFT id for the user
* Showing in your website that that the mint has been successful
### Ways to check the status of an action
| Model | Best for | Mechanism |
| ------------------------ | --------------------------- | ----------------- |
| Pull (query for updates) | Quick testing | Action Status API |
| Push (get notified) | Scalable apps in production | Webhooks |
## Action Status APIs
Call the following API to check the status of an action:
```bash cURL theme={null}
# Set your variables
env=staging # or "www" for production
YOUR_API_KEY=
actionId=
# Execute the curl command
curl --request GET \
--url "https://${env}.crossmint.com/api/2022-06-09/actions/${actionId}" \
--header 'accept: application/json' \
--header "x-api-key: ${YOUR_API_KEY}"
```
* `actionID` is returned from any async API calls you perform.
* `YOUR_API_KEY` can be found in the `Developers -> API Keys` tab of the [Production](https://www.crossmint.com/console/projects/apiKeys) or [Staging](https://staging.crossmint.com/console/projects/apiKeys) consoles.
## Webhooks
In this guide, we will use nodejs to create an API endpoint to listen for and parse webhook events:
### 1. Create an endpoint route
Using a standard nodejs API server, create an endpoint.
You can test locally by installing [ngrok](https://ngrok.com/docs/getting-started/) and creating a routed endpoint to a specified port.
### 2. Configure the endpoint to read and parse webhook events
On this endpoint, modify the code to handle POST requests only. When a POST request comes through, parse the webhook event of the request body. Ensure your webhook listener responds with a `200` status code. Otherwise the webhook may be sent until you acknowledge it.
The snippet below is an example handler that parses webhook events:
```javascript theme={null}
// endpoint.js
// listen to webhook ingestion
export default function handler(req, res) {
if (req.method === "POST") {
console.log(`[webhook] Successfully minted ${req.body.id}`);
}
res.status(200).json({});
}
```
Don't be strict with payload validations as Crossmint may add new fields to
the webhooks as products evolve.
Here are some examples of the webhook results (with dummy data):
```json EVM theme={null}
{
"actionId": "897eadae-ee2d-43f9-a97b-1a9d9c682d6f",
"startedAt": "2023-10-04T15:48:20.000Z",
"completedAt": "2023-10-04T15:48:42.000Z",
"type": "collections.create.succeeded",
"data": {
"chain": "polygon",
"txId": "0x919ec33c1ceffd292d1d0cdd9675ffcc0af31a1f34622edd4865a9ca9fa82aa1",
"collection": {
"id": "897eadae-ee2d-43f9-a97b-1a9d9c682d6f",
"contractAddress": "0x59195995f248450267AD40CAc1d79fAAba290467"
}
},
"resource": "https://crossmint.com/api/2022-06-09/collections/897eadae-ee2d-43f9-a97b-1a9d9c682d6f"
}
```
```json Solana theme={null}
{
"actionId": "01ac4b77-9bc9-42d3-a110-b0572ec299fd",
"startedAt": "2023-10-04T15:50:12.000Z",
"completedAt": "2023-10-04T15:50:23.000Z",
"type": "collections.create.succeeded",
"data": {
"chain": "solana",
"txId": "2M4tFbGWFPWAjHmZdssoTiTRjxx43nRd3vt9RpQ3wLWsW69duUhvEQrDvsyfafnQNoh6LmDQg4KPLvffgCskkwoV",
"collection": {
"id": "01ac4b77-9bc9-42d3-a110-b0572ec299fd",
"mintAddress": "JCpAQcjqWCwTpRPsMumsXtg3A4SGMXbxVbowu8qrntqt"
}
},
"resource": "https://a5b6-181-167-232-117.ngrok-free.app/api/2022-06-09/collections/01ac4b77-9bc9-42d3-a110-b0572ec299fd"
}
```
```json EVM theme={null}
{
"actionId": "e0926e73-8212-4074-b934-4d6ac68cfcff",
"startedAt": "2023-10-04T15:57:14.000Z",
"completedAt": "2023-10-04T15:57:36.000Z",
"type": "collections.update.succeeded",
"data": {
"chain": "polygon",
"txId": "0x919ec33c1ceffd292d1d0cdd9675ffcc0af31a1f34622edd4865a9ca9fa82aa1",
"collection": {
"id": "897eadae-ee2d-43f9-a97b-1a9d9c682d6f",
"contractAddress": "0x59195995f248450267AD40CAc1d79fAAba290467"
},
"changes": ["supplyLimit"]
},
"resource": "https://crossmint.com/api/2022-06-09/collections/e0926e73-8212-4074-b934-4d6ac68cfcff",
"timestamp": 1696424259573
}
```
```json EVM theme={null}
{
"type": "nfts.create.succeeded",
"actionId": "771d7e38-2890-47d7-b733-a1462736b528",
"startedAt": "2023-10-04T19:59:47.000Z",
"completedAt": "2023-10-04T20:00:00.000Z",
"data": {
"chain": "polygon",
"txId": "0x919ec33c1ceffd292d1d0cdd9675ffcc0af31a1f34622edd4865a9ca9fa82aa1",
"collection": {
"id": "default-polygon",
"contractAddress": "0x2DdDDEe8dad8b2ec123F5ceEc3E8dA4E57C0ed29"
},
"recipient": {
"walletAddress": "0x10324e5B8879CA6662ff83617F74b0AaD251b819",
"email": "recipient@crossmint.com"
},
"token": {
"id": "771d7e38-2890-47d7-b733-a1462736b528",
"owner": {
"walletAddress": "0x10324e5B8879CA6662ff83617F74b0AaD251b819"
},
"tokenId": "15"
}
},
"resource": "https://crossmint.com/api/2022-06-09/collections/default-polygon/nfts/771d7e38-2890-47d7-b733-a1462736b528"
}
```
```json Solana theme={null}
{
"type": "nfts.create.succeeded",
"actionId": "2c5f9ec6-ae68-4e17-a671-db4af2208a2d",
"startedAt": "2023-10-04T20:01:21.000Z",
"completedAt": "2023-10-04T20:01:27.000Z",
"data": {
"chain": "solana",
"txId": "2M4tFbGWFPWAjHmZdssoTiTRjxx43nRd3vt9RpQ3wLWsW69duUhvEQrDvsyfafnQNoh6LmDQg4KPLvffgCskkwoV",
"collection": {
"id": "default-solana",
"mintAddress": "qCTMcsRBYYt1YKDAkJnstjgXJU625YX5LBfwsUi1Mn4"
},
"recipient": {
"walletAddress": "EjLpq6RAxzPuwjcwJ2C6ZjwrFZuqhu2CiVTDzkSSCE5B",
"email": "recipient@crossmint.com"
},
"token": {
"id": "2c5f9ec6-ae68-4e17-a671-db4af2208a2d",
"owner": {
"walletAddress": "EjLpq6RAxzPuwjcwJ2C6ZjwrFZuqhu2CiVTDzkSSCE5B"
},
"mintHash": "Gec66rG8Rj6Q2nqaFpGRvggiAfriMb93o28qGSeeY1gJ"
}
},
"resource": "https://staging.crossmint.com/api/2022-06-09/collections/default-solana/nfts/2c5f9ec6-ae68-4e17-a671-db4af2208a2d",
"timestamp": 1696449687501
}
```
```json EVM theme={null}
{
"actionId":"cf9985d3-3341-4fd6-bd0e-548ef97a3486",
"startedAt":"2024-02-05T21:48:17.000Z",
"type":"nfts.create.failed",
"data":{
"chain":"polygon",
"collection":{
"id":"297199ba-c763-4464-b592-26c2ca2dbe5d",
"contractAddress":"0x2ADBeb5e1976615883D3c5F07234E38b50e09edB"
},
"contractArguments":{
"key":28
},
"recipient":{
"walletAddress":"0x78359E7dF948834caFEcBE0494B010C6f7f7fA74"
},
"token":{
"id":"cf9985d3-3341-4fd6-bd0e-548ef97a3486"
},
"error":{
"reason":"execution_reverted",
"message":"Minting smart contract reverted. Check 'revertReason' for details",
"revertReason":"execution reverted: Wrong value for key",
"callInfo":{
"functionName":"mintTo",
"arguments":{
"key":28,
"recipient":"0x78359E7dF948834caFEcBE0494B010C6f7f7fA74"
},
"calldata":"0x449a52f800000000000000000000000078359e7df948834cafecbe0494b010c6f7f7fa74000000000000000000000000000000000000000000000000000000000000001c"
}
}
},
"resource":"https://1a7a-181-110-64-58.ngrok-free.app/api/2022-06-09/collections/297199ba-c763-4464-b592-26c2ca2dbe5d/nfts/cf9985d3-3341-4fd6-bd0e-548ef97a3486",
"timestamp":1707158908733
}
```
```json EVM theme={null}
{
"type": "nfts.update.succeeded",
"actionId": "f7fedd17-2feb-4bc9-88fa-8890ea8f99e8",
"startedAt": "2023-10-04T20:09:27.000Z",
"completedAt": "2023-10-04T20:09:35.000Z",
"data": {
"chain": "polygon",
"txId": "0x919ec33c1ceffd292d1d0cdd9675ffcc0af31a1f34622edd4865a9ca9fa82aa1",
"collection": {
"id": "default-polygon",
"contractAddress": "0x2DdDDEe8dad8b2ec123F5ceEc3E8dA4E57C0ed29"
},
"token": {
"id": "210a95ab-d59c-41ef-9f60-8e41550a753e",
"owner": {
"walletAddress": "0x10324e5B8879CA6662ff83617F74b0AaD251b819"
},
"tokenId": "16"
},
"changes": ["metadata"]
},
"resource": "https://crossmint.com/api/2022-06-09/collections/default-polygon/nfts/f7fedd17-2feb-4bc9-88fa-8890ea8f99e8",
"timestamp": 1696450175660
}
```
```json Solana theme={null}
{
"type": "nfts.update.succeeded",
"actionId": "57d3cedd-e8e8-4509-88bc-97d6baea478f",
"startedAt": "2023-10-04T20:07:55.000Z",
"completedAt": "2023-10-04T20:08:01.000Z",
"data": {
"chain": "solana",
"txId": "2M4tFbGWFPWAjHmZdssoTiTRjxx43nRd3vt9RpQ3wLWsW69duUhvEQrDvsyfafnQNoh6LmDQg4KPLvffgCskkwoV",
"collection": {
"id": "default-solana",
"mintAddress": "qCTMcsRBYYt1YKDAkJnstjgXJU625YX5LBfwsUi1Mn4"
},
"token": {
"id": "1f3f0959-417d-43a7-95f8-39e6f22d5573",
"owner": {
"walletAddress": "EjLpq6RAxzPuwjcwJ2C6ZjwrFZuqhu2CiVTDzkSSCE5B"
},
"mintHash": "BnyGQcmU4mXMi5NMUuKTjFdYkg79RZoBX3qCWdXZVcyb"
},
"changes": ["metadata"]
},
"resource": "https://crossmint.com/api/2022-06-09/collections/default-solana/nfts/57d3cedd-e8e8-4509-88bc-97d6baea478f",
"timestamp": 1696450081841
}
```
### 3. Pre & post processing
Add your pre and post processing logic when setting up your webhook listener. For example, you can call back to your database when a certain id has succeeded or even use Sendgrid or EmailJS to send an email to a recipient when a mint completes.
### 4. Setting Up Webhooks on the Crossmint Console
Add an endpoint for the event `mint.succeeded` by following [this guide](/introduction/platform/webhooks/add-endpoint).
Once completed, you'll be redirected to the endpoint details page. Here, you can find the signing secret for [verifying webhooks](/introduction/platform/webhooks/verify-webhooks) and view a table of all triggered webhook events.
The final step is to test the endpoint. To do so, mint one or two NFTs with the API and observe the responses to verify setup success.
## Webhook video walkthrough
# Quickstart ⚡
Source: https://docs.crossmint.com/minting/quickstarts/credentials
Mint credentials that users control, share, and verify anywhere in under 5 minutes
In this quickstart you will learn how to create, issue, and verify credentials.
## What are credentials?
A digital credential is an electronic record issued by a trusted source that certifies specific information about a person or entity. Crossmint makes it easy to issue, manage, and verify credentials onchain, following the [W3C VC standard](https://www.w3.org/TR/vc-data-model-2.0/).
Verifiable Credentials is an Enterprise feature. Contact Sales
for access.
## Integration steps
### 1. Create a Developer Account
### 2. Get an API Key
Within the "Server-side keys" section, click the "Create new key" button in the top right.
Then, check the scopes `credentials.create`, `credentials.read`, and `credentials:templates.create` under the "Verifiable Credentials"
category and create your key. Save this key for the next step.
### 3. Create a Credential Template
Every Verifiable Credential must belong to a template. Verifiable Credentials within a template share the same schema (referred to as "type" in the template definition), default-metadata, encryption, storage, and chain configurations. A credential template is equivalent to an NFT collection.
For the purpose of this quickstart, we will create a template with the following parameters:
* Type: `crossmint:bedea4c5-4cea-425b-99c7-797ecbc8bc13:CourseCompletionCertificate`. Types allow you to specify the attributes you are interested in certifying. They also act as a protective measure, preventing the addition of unauthorized fields and, as a result, the tampering of the Verifiable Credential. You can [define your own](/minting/verifiable-credentials/integrate/create-credential-types).
* Encryption: `none`. This means the Verifiable Credential data will be stored in plain text. To encrypt the data, read about the supported [encryption modalities](/minting/verifiable-credentials/integrate/encrypt-credentials).
* Storage: `crossmint`. This means the Verifiable Credential data will be stored by Crossmint. To store the data at the location of your choice, or in decentralized storage, read about the supported [storage modalities](/minting/verifiable-credentials/integrate/store-credentials).
The credential’s revocation state is stored in the chain provided (`polygon-amoy`), along with public (non-confidential) metadata related to your credential’s template.
```javascript createTemplate.js theme={null}
const apiKey = "YOUR_API_KEY";
const env = "staging"; // or "www"
const url = `https://${env}.crossmint.com/api/v1-alpha1/credentials/templates`;
const options = {
method: "POST",
headers: {
accept: "application/json",
"content-type": "application/json",
"x-api-key": apiKey,
},
body: JSON.stringify({
credentials: {
type: "crossmint:bedea4c5-4cea-425b-99c7-797ecbc8bc13:CourseCompletionCertificate",
encryption: "none",
storage: "crossmint",
subject: {
properties: {
course: {
type: "string",
title: "Course",
description: "Course name",
},
grade: {
type: "string",
title: "Grade",
description: "Grade received",
},
},
required: ["course", "grade"],
},
},
metadata: {
name: "Certificate of Completion",
description: "A certificate issued upon completion of a course",
imageUrl: "https://www.crossmint.com/assets/crossmint/logo.png",
},
chain: "polygon-amoy",
}),
};
fetch(url, options)
.then((res) => res.json())
.then((json) => console.log(json))
.catch((err) => console.error("error:" + err));
```
```json JSON theme={null}
{
"id": "a9e3016b-66f0-40ab-8d72-6546fddc7b72",
"metadata": {
"description": "A certificate issued upon completion of a course",
"name": "Certificate of Completion",
"imageUrl": "ipfs://QmaToZn4VEjF7q4CAudPaNka6AD484xuuEZSXmTLJPDLVE"
},
"fungibility": "non-fungible",
"onChain": { "chain": "polygon-amoy", "type": "erc-721" },
"subscription": { "enabled": false },
"actionId": "a9e3016b-66f0-40ab-8d72-6546fddc7b72"
}
```
Save the `id` from the response as your `TEMPLATE_ID` for the next step.
### 4. Issue a Credential
With a template created, we can start issuing credentials. To do this, we need to enter the subject’s email address, or wallet address, if they have one.
Then, we need to specify the exact data required by the Verifiable Credential type, i.e. course name and grade. The credential’s contents are identified by the “subject” key within the credential.
If you don't include an expiration date, the credential will not expire. You can always revoke the credential in the
future, if necessary.
```javascript issueCredential.js theme={null}
const apiKey = "YOUR_API_KEY";
const env = "staging"; // or "www"
const templateId = "YOUR_TEMPLATE_ID";
const recipientEmail = "TEST_EMAIL_ADDRESS";
const url = `https://${env}.crossmint.com/api/v1-alpha1/credentials/templates/${templateId}/vcs`;
const options = {
method: "POST",
headers: {
accept: "application/json",
"content-type": "application/json",
"x-api-key": apiKey,
},
body: JSON.stringify({
recipient: `email:${recipientEmail}:polygon-amoy`,
credential: {
// The CourseCompletionCertificate credential type requires course and grade declarations
subject: {
course: "Blockchain 101",
grade: "A",
},
expiresAt: "2034-02-02",
},
}),
};
fetch(url, options)
.then((res) => res.json())
.then((json) => console.log(json))
.catch((err) => console.error("error:" + err));
```
```json JSON theme={null}
{
"id": "6728f773-61af-4732-a34f-b5a50d8b9872",
"onChain": {
"status": "pending",
"chain": "polygon-amoy",
"contractAddress": "0x45c9597b5441289C134E554990cdb647f65df5FB"
},
"credentialId": "urn:uuid:e6b3dc76-a337-437b-ac1b-3d330e66e1a6",
"actionId": "6728f773-61af-4732-a34f-b5a50d8b9872"
}
```
Save the credential `id` from the response for the next step.
### 5. Retrieve a Credential
You can retrieve a Verifiable Credential using different identifiers associated with the credential itself or the NFT associated with the credential.
* [Get Verifiable Credential by ID](/api-reference/verifiable-credentials/credentials/retrieve-credential-by-id) uses the format: `urn:uuid:`
* [Get Verifiable Credential by NFT Locator](/api-reference/verifiable-credentials/credentials/retrieve-credential-by-nft-locator) uses the format: `::`
* [Get Verifiable Credential by NFT ID](/api-reference/verifiable-credentials/credentials/retrieve-credential-by-nft) uses crossmint's internal NFT ID
```javascript getCredential.js theme={null}
const apiKey = "YOUR_API_KEY";
const env = "staging"; // or "www"
const credentialId = "YOUR_CREDENTIAL_ID";
const url = `https://${env}.crossmint.com/api/v1-alpha1/credentials/${credentialId}`;
const options = {
method: "GET",
headers: {
accept: "application/json",
"x-api-key": apiKey,
},
};
fetch(url, options)
.then((res) => res.json())
.then((json) => console.log(json))
.catch((err) => console.error("error:" + err));
```
```json JSON theme={null}
{
"unencryptedCredential": {
"id": "urn:uuid:e6b3dc76-a337-437b-ac1b-3d330e66e1a6",
"credentialSubject": {
"course": "Blockchain 101",
"grade": "A",
"id": "did:polygon-amoy:0x2da3151C266185c4861b45277dbbe56Cb613963e"
},
"validUntil": "2034-02-02",
"nft": {
"tokenId": "1",
"chain": "polygon-amoy",
"contractAddress": "0x45c9597b5441289C134E554990cdb647f65df5FB"
},
"issuer": {
"id": "did:polygon-amoy:0x1785A5DE9e0F06791393739De496a0a2c9ACA855"
},
"type": ["VerifiableCredential", "Course completion"],
"validFrom": "2025-05-28T14:31:03.514Z",
"@context": ["https://www.w3.org/ns/credentials/v2"],
"proof": {
"verificationMethod": "did:polygon-amoy:0x1785A5DE9e0F06791393739De496a0a2c9ACA855#evmAddress",
"created": "2025-05-28T14:31:03.514Z",
"proofPurpose": "assertionMethod",
"type": "EthereumEip712Signature2021",
"proofValue": "0x17f624a23b620de2867dbefbeb5214e5693a4b470897302f91f870383d4f5ee7754600d1fb72b73c926fed56aac20724ce87d49e0078e0b13f02734a199ceaa61b",
"eip712": [Object]
}
}
}
```
### 6. Verify a Credential
Verifying a credential can be done in different ways, the easiest one is to call the verify API route. You can also accomplish
this with the [SDK](/sdk-reference/credentials/introduction).
To verify a credential's validity via API use the following script:
```javascript verifyCredential.js theme={null}
const apiKey = "YOUR_API_KEY";
const env = "staging"; // or "www"
const credentialId = "YOUR_CREDENTIAL_ID";
const url = `https://${env}.crossmint.com/api/v1-alpha1/credentials/verification/verify`;
const options = {
method: "POST",
headers: {
"accept": "application/json",
"content-type": "application/json",
"x-api-key": apiKey,
},
body: JSON.stringify({
// paste credential object here
credential : {
id: "urn:uuid:e6b3dc76-a337-437b-ac1b-3d330e66e1a6",
...
}
})
};
fetch(url, options)
.then((res) => res.json())
.then((json) => console.log(json))
.catch((err) => console.error("error:" + err));
```
```json JSON theme={null}
{ isValid: true }
```
### 7. Revoke a Credential (Optional)
To revoke a credential, you can directly use the [revoke credential API](/api-reference/verifiable-credentials/credentials/revoke-credential):
```javascript revokeCredential.js theme={null}
const apiKey = "YOUR_API_KEY";
const env = "staging"; // or "www"
const credentialId = "YOUR_CREDENTIAL_ID";
const url = `https://${env}.crossmint.com/api/v1-alpha1/credentials/${credentialId}`;
const options = {
method: "DELETE",
headers: {
accept: "application/json",
"x-api-key": apiKey,
},
};
fetch(url, options)
.then((res) => res.json())
.then((json) => console.log(json))
.catch((err) => console.error("error:" + err));
```
```json JSON theme={null}
{
"actionId": "15bfe43c-3d5d-4d03-9e20-6dbaf01f038f",
"action": "nfts.delete",
"status": "pending",
"data": {
"chain": "polygon-amoy",
"collection": {
"id": "a9e3016b-66f0-40ab-8d72-6546fddc7b72",
"contractAddress": "0x45c9597b5441289C134E554990cdb647f65df5FB"
},
"token": {
"id": "6728f773-61af-4732-a34f-b5a50d8b9872",
"tokenId": "1"
}
},
"startedAt": "2025-05-28T16:53:11.000Z",
"resource": "https://staging.crossmint.com/api/2022-06-09/collections/a9e3016b-66f0-40ab-8d72-6546fddc7b72/nfts/15bfe43c-3d5d-4d03-9e20-6dbaf01f038f"
}
```
## Learn More
For more detailed information about Verifiable Credentials, please visit the dedicated [Verifiable Credentials section](/verifiable-credentials/introduction) which includes comprehensive guides on:
Learn how to define the structure of your credentials.
Create reusable templates for your credentials.
Issue credentials to users.
Verify the authenticity of credentials.
# Quickstart ⚡
Source: https://docs.crossmint.com/minting/quickstarts/ip
Mint IP credentials for creators to secure their work in under 5 minutes
In this quickstart you will learn how to create and manage intellectual property (IP) assets.
Contact [sales](https://www.crossmint.com/contact/sales) to enable this API on your project.
## What are IP credentials?
IP credentials are onchain records of intellectual property rights and authorship, enabled by Story Protocol's infrastructure and issued using Crossmint's APIs.
## Integration steps
### 1. Create a Developer Account
### 2. Get an API Key
Within the "Server-side keys" section, click the "Create new key" button in the top right.
Then, check the necessary scopes under the "Minting API" category: `nfts.create`, `nfts.read`, `collections.create`, `nfts.update` and create your key. Save this key for the next step.
### 3. Create an IP Collection
First, let's create an IP collection to hold our IP assets:
```javascript createIPCollection.js theme={null}
const apiKey = "YOUR_API_KEY";
const env = "staging"; // or "www"
const url = `https://${env}.crossmint.com/api/v1/ip/collections`;
const options = {
method: "POST",
headers: {
accept: "application/json",
"content-type": "application/json",
"x-api-key": apiKey,
},
body: JSON.stringify({
metadata: {
description: "A collection of intellectual property assets",
name: "My IP Collection",
symbol: "MPA",
},
chain: "story-testnet",
}),
};
fetch(url, options)
.then((res) => res.json())
.then((json) => console.log(json))
.catch((err) => console.error("error:" + err));
```
Here is an example response returned from the API call above:
```json JSON theme={null}
{
"id": "7f5e653d-2c9d-4025-98c8-8245c128ad10",
"actionId": "7f5e653d-2c9d-4025-98c8-8245c128ad10",
"metadata": {
"name": "My IP Collection",
"symbol": "MPA",
"description": "A collection of intellectual property assets"
},
"onChain": {
"chain": "story-testnet"
}
}
```
Save the collection `id` from the response for the next step.
### 4. Create an IP Asset
Now, let's create an IP asset within our collection:
```javascript createIPAsset.js theme={null}
const apiKey = "YOUR_API_KEY";
const env = "staging"; // or "www"
const collectionId = "YOUR_COLLECTION_ID";
const url = `https://${env}.crossmint.com/api/v1/ip/collections/${collectionId}/ipassets`;
const options = {
method: "POST",
headers: {
accept: "application/json",
"content-type": "application/json",
"x-api-key": apiKey,
},
body: JSON.stringify({
owner: "email:creator@example.com:story-testnet",
nftMetadata: {
name: "Snowflake Funk",
description: "A disco song for the winter holidays",
image: "https://cdn2.suno.ai/image_large_c001fd6e-d6cd-474f-a7b6-6e6a9b3e2515.jpeg",
},
ipAssetMetadata: {
title: "Snowflake Funk",
createdAt: "2025-02-11T11:13:00",
ipType: "music",
image: "https://cdn2.suno.ai/image_large_example.jpeg",
imageHash: "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
mediaUrl: "https://cdn1.suno.ai/c001fd6e-d6cd-474f-a7b6-6e6a9b3e2515.mp3",
mediaHash: "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
mediaType: "audio/mpeg",
creators: [
{
name: "John Doe",
email: "john.doe@example.com",
crossmintUserLocator: "email:john.doe@example.com:story-testnet",
contributionPercent: 100,
},
],
media: [
{
name: "Snowflake Funk",
url: "https://cdn1.suno.ai/c001fd6e-d6cd-474f-a7b6-6e6a9b3e2515.mp3",
mimeType: "audio/mpeg",
},
],
attributes: [
{
key: "Suno Artist",
value: "InfluentialCoda427",
},
{
key: "Source",
value: "Suno.com",
},
],
},
}),
};
fetch(url, options)
.then((res) => res.json())
.then((json) => console.log(json))
.catch((err) => console.error("error:" + err));
```
Here is an example response returned from the API call above:
```json JSON theme={null}
{
"id": "49b9e141-8fd8-45dc-9646-13a3439c6f20",
"actionId": "49b9e141-8fd8-45dc-9646-13a3439c6f20",
"nftMetadata": {
"name": "Snowflake Funk",
"image": "https://cdn2.suno.ai/image_large_c001fd6e-d6cd-474f-a7b6-6e6a9b3e2515.jpeg",
"description": "A disco song for the winter holidays"
},
"ipAssetMetadata": {
"title": "Snowflake Funk",
"createdAt": "2025-02-11T11:13:00",
"creators": [
{
"name": "John Doe",
"email": "john.doe@example.com",
"crossmintUserLocator": "email:john.doe@example.com:story-testnet",
"contributionPercent": 100
}
],
"media": [
{
"name": "Snowflake Funk",
"url": "https://cdn1.suno.ai/c001fd6e-d6cd-474f-a7b6-6e6a9b3e2515.mp3",
"mimeType": "audio/mpeg"
}
],
"attributes": [
{
"key": "Suno Artist",
"value": "InfluentialCoda427"
},
{
"key": "Source",
"value": "Suno.com"
}
],
"ipType": "music"
},
"licenseTerms": [
{
"type": "non-commercial-social-remixing"
}
],
"onChain": {
"chain": "story-testnet",
"contractAddress": "0x82a72DfFb175DF8AB6d3C2E3888D5314a50C30D0",
"status": "pending"
}
}
```
Save the IP asset `id` from the response for the next steps.
### 5. Retrieve an IP Asset
To retrieve an IP asset:
```javascript getIPAsset.js theme={null}
const apiKey = "YOUR_API_KEY";
const env = "staging"; // or "www"
const collectionId = "YOUR_COLLECTION_ID";
const ipAssetId = "YOUR_IP_ASSET_ID";
const url = `https://${env}.crossmint.com/api/v1/ip/collections/${collectionId}/ipassets/${ipAssetId}`;
const options = {
method: "GET",
headers: {
accept: "application/json",
"x-api-key": apiKey,
},
};
fetch(url, options)
.then((res) => res.json())
.then((json) => console.log(json))
.catch((err) => console.error("error:" + err));
```
Here is an example response returned from the API call above:
```json JSON theme={null}
{
"id": "49b9e141-8fd8-45dc-9646-13a3439c6f20",
"actionId": "49b9e141-8fd8-45dc-9646-13a3439c6f20",
"nftMetadata": {
"name": "Snowflake Funk",
"image": "ipfs://QmYA9HyvoPmxvsMx216MFkoydtrA1sSxX5A5ixeu5S9VKF",
"description": "A disco song for the winter holidays"
},
"ipAssetMetadata": {
"title": "Snowflake Funk",
"createdAt": "2025-02-11T11:13:00",
"creators": [
{
"name": "John Doe",
"email": "john.doe@example.com",
"crossmintUserLocator": "email:john.doe@example.com:story-testnet",
"contributionPercent": 100
}
],
"media": [
{
"name": "Snowflake Funk",
"url": "https://cdn1.suno.ai/c001fd6e-d6cd-474f-a7b6-6e6a9b3e2515.mp3",
"mimeType": "audio/mpeg"
}
],
"attributes": [
{
"key": "Suno Artist",
"value": "InfluentialCoda427"
},
{
"key": "Source",
"value": "Suno.com"
}
],
"ipType": "music"
},
"licenseTerms": [
{
"type": "non-commercial-social-remixing",
"uri": ""
}
],
"onChain": {
"chain": "story-testnet",
"contractAddress": "0x82a72DfFb175DF8AB6d3C2E3888D5314a50C30D0",
"status": "success",
"ipAssetId": "0xe20936B1C974E7C550b483243719A1B1886D2c6c",
"tokenId": "1",
"txId": "0x4e505bcd429960d3031bd8d1f27bb7ffd2cfa251498c12c16cf2375a14d2bd51",
"owner": "0x1a042BBEFb116072E6AEF8304f9B7D15aA2b4BCF",
"explorerLink": "https://aeneid.explorer.story.foundation/ipa/0xe20936B1C974E7C550b483243719A1B1886D2c6c"
}
}
```
### 6. Update an IP Asset
If needed, you can update an IP asset:
```javascript updateIPAsset.js theme={null}
const apiKey = "YOUR_API_KEY";
const env = "staging"; // or "www"
const collectionId = "YOUR_COLLECTION_ID";
const ipAssetId = "YOUR_IP_ASSET_ID";
const url = `https://${env}.crossmint.com/api/v1/ip/collections/${collectionId}/ipassets/${ipAssetId}`;
const options = {
method: "PATCH",
headers: {
accept: "application/json",
"content-type": "application/json",
"x-api-key": apiKey,
},
body: JSON.stringify({
ipAssetMetadata: {
title: "Snowflake Funk",
createdAt: "2025-02-11T11:13:00",
ipType: "music",
creators: [
{
name: "John Doe",
email: "john.doe@example.com",
crossmintUserLocator: "email:john.doe@example.com:story-testnet",
contributionPercent: 90,
},
{
name: "Frank L",
crossmintUserLocator: "email:frank.l@example.com:story-testnet",
contributionPercent: 10,
},
],
},
}),
};
fetch(url, options)
.then((res) => res.json())
.then((json) => console.log(json))
.catch((err) => console.error("error:" + err));
```
Here is an example response returned from the API call above:
```json JSON theme={null}
{
"id": "75a45061-febb-4344-84d6-959276a5696a",
"actionId": "75a45061-febb-4344-84d6-959276a5696a",
"nftMetadata": {
"name": "Snowflake Funk",
"image": "ipfs://QmYA9HyvoPmxvsMx216MFkoydtrA1sSxX5A5ixeu5S9VKF",
"description": "A disco song for the winter holidays"
},
"ipAssetMetadata": {
"title": "Snowflake Funk",
"createdAt": "2025-02-11T11:13:00",
"creators": [ ... ],
"ipType": "music"
},
"licenseTerms": [ { "type": "non-commercial-social-remixing", "uri": "" } ],
"onChain": {
"chain": "story-testnet",
"contractAddress": "0x82a72DfFb175DF8AB6d3C2E3888D5314a50C30D0",
"status": "pending"
}
}
```
## Learn More
For more detailed information about IP assets, please refer to the API reference:
Learn how to create IP collections.
Create IP assets within collections.
Update existing IP assets.
Retrieve IP assets from collections.
# Non-Fungible Tokens (NFTs)
Source: https://docs.crossmint.com/minting/quickstarts/nfts
Mint an NFT in under 5 minutes
In this quickstart you will learn how to create a unique NFT and deliver it to a wallet or email address.
## Integration steps
### 1. Create a Developer Account
### 2. Get an API Key
Within the "Server-side keys" section, click the "Create new key" button in the top right.
Then, check the scopes `nfts.create` and `nfts.read` under the "Minting API"
category and create your key. Save this key for the next step.
### 3. Mint an NFT
We're almost there! With our key created, we're now going to write a small
function that creates an NFT and delivers it to a user.
Create a file (e.g. `mintNFT.js`) and enter this code into it:
```javascript mintNFT.js theme={null}
const apiKey = "YOUR_API_KEY";
const chain = "polygon-amoy"; // or "ethereum-sepolia", "base-sepolia", etc.
const env = "staging"; // or "www"
const recipientEmail = "TEST_EMAIL_ADDRESS";
const recipientAddress = `email:${recipientEmail}:${chain}`;
const url = `https://${env}.crossmint.com/api/2022-06-09/collections/default/nfts`;
const options = {
method: "POST",
headers: {
"accept": "application/json",
"content-type": "application/json",
"x-api-key": apiKey,
},
body: JSON.stringify({
recipient: recipientAddress,
metadata: {
name: "Crossmint Test NFT",
image: "https://picsum.photos/400",
description: "My first NFT using Crossmint",
},
}),
};
fetch(url, options)
.then((res) => res.json())
.then((json) => console.log(json))
.catch((err) => console.error("error:" + err));
```
This code creates an NFT, uploads it to the blockchain, and delivers it to a user wallet, attached to the email address provided. Before running it, be sure to fill in values for:
* `YOUR_API_KEY` with the key obtained in the prior step.
* `TEST_EMAIL_ADDRESS` with an email address you control, for testing.
Now, run the `mintNFT.js` script.
```bash theme={null}
node mintNFT.js
```
After a few seconds, it should return a response indicating the mint has
started processing. Check the full response and save its `actionId` property, as you'll use it later.
Here is an example response returned from the API call above:
```json JSON theme={null}
{
"id": "b3f61f22-c30e-424d-a5ab-d0cbc5c41aab",
"onChain": {
"status": "pending",
"chain": "polygon-amoy",
"contractAddress": "0x921Bc21bf3Fd6568cdC2C904F75b83556062c3d0"
},
"actionId": "b3f61f22-c30e-424d-a5ab-d0cbc5c41aab"
}
```
### 4. Confirm Delivery of the NFT
The mint has started processing. However, blockchains can take a few seconds (or,
at times of extreme network congestion, even minutes) to confirm the operation.
Before showing the user a success screen, the next step is checking the
status of the mint.
To do this, grab the `actionId` received at the end of step 3 and use it
alongside your API key in one of the snippets below.
```javascript mintStatus.js theme={null}
const apiKey = "";
const env = "staging"; // or "www"
const actionId = "";
const url = `https://${env}.crossmint.com/api/2022-06-09/actions/${actionId}`;
const options = {
method: "GET",
headers: { "X-API-KEY": apiKey },
};
fetch(url, options)
.then((response) => response.json())
.then((response) => console.log(response))
.catch((err) => console.error(err));
```
```bash cURL theme={null}
# Replace "staging" with "www" for production, and fill in your api key and mint action id
ENV="staging"
API_KEY=""
MINT_ACTION_ID=""
curl --header "x-api-key: $API_KEY" \
-X GET \
https://${ENV}.crossmint.com/api/2022-06-09/actions/${MINT_ACTION_ID}
```
Here is an example response from calling the status API:
```json JSON theme={null}
{
"actionId": "b3f61f22-c30e-424d-a5ab-d0cbc5c41aab",
"action": "nfts.create",
"status": "success",
"data": {
"collection": {...},
"recipient": {...},
"token": {...}
},
"startedAt": "2025-05-27T02:41:08.000Z",
"completedAt": "2025-05-27T02:41:40.000Z",
"resource": "https://staging.crossmint.com/api/2022-06-09/collections/default-polygon-amoy/nfts/b3f61f22-c30e-424d-a5ab-d0cbc5c41aab"
}
```
Pay attention to the "status" field. Once it says "success":
**Congratulations. You have minted your first NFT** 🥷 🎉
For scalable production applications, consider using [webhooks](/minting/nfts/integrate/webhooks-and-status-apis) to
determine when your NFT has been minted, instead of periodically polling for its status via the API.
### 1. Create a Developer Account
### 2. Get an API Key
Within the "Server-side keys" section, click the "Create new key" button in the top right.
Then, check the scopes `nfts.create` and `nfts.read` under the "Minting API"
category and create your key. Save this key for the next step.
### 3. Mint an NFT
We're almost there! With our key created, we're now going to write a small
function that creates an NFT and delivers it to a user.
Create a file (e.g. `mintNFT.js`) and enter this code into it:
```javascript mintNFT.js theme={null}
const apiKey = "YOUR_API_KEY";
const chain = "solana";
const env = "staging"; // or "www"
const recipientEmail = "TEST_EMAIL_ADDRESS";
const recipientAddress = `email:${recipientEmail}:${chain}`;
const url = `https://${env}.crossmint.com/api/2022-06-09/collections/default-solana/nfts`;
const options = {
method: "POST",
headers: {
"accept": "application/json",
"content-type": "application/json",
"x-api-key": apiKey,
},
body: JSON.stringify({
recipient: recipientAddress,
metadata: {
name: "Crossmint Test NFT",
image: "https://picsum.photos/400",
description: "My first NFT using Crossmint",
},
}),
};
fetch(url, options)
.then((res) => res.json())
.then((json) => console.log(json))
.catch((err) => console.error("error:" + err));
```
This code creates an NFT, uploads it to the Solana blockchain, and delivers it to a user wallet, attached to the email address provided. Before running it, be sure to fill in values for:
* `YOUR_API_KEY` with the key obtained in the prior step.
* `TEST_EMAIL_ADDRESS` with an email address you control, for testing.
Now, run the `mintNFT.js` script.
```bash theme={null}
node mintNFT.js
```
After a few seconds, it should return a response indicating the mint has
started processing. Check the full response and save its `actionId` property, as you'll use it later.
Here is an example response returned from the API call above:
```json JSON theme={null}
{
"id": "cace8d7d-e4dd-483d-a780-faf6aa9f761a",
"onChain": {
"status": "pending",
"chain": "solana"
},
"actionId": "cace8d7d-e4dd-483d-a780-faf6aa9f761a"
}
```
### 4. Confirm Delivery of the NFT
The mint has started processing. However, blockchains can take a few seconds (or,
at times of extreme network congestion, even minutes) to confirm the operation.
Before showing the user a success screen, the next step is checking the
status of the mint.
To do this, grab the `actionId` received at the end of step 3 and use it
alongside your API key in one of the snippets below.
```javascript mintStatus.js theme={null}
const apiKey = "";
const env = "staging"; // or "www"
const actionId = "";
const url = `https://${env}.crossmint.com/api/2022-06-09/actions/${actionId}`;
const options = {
method: "GET",
headers: { "X-API-KEY": apiKey },
};
fetch(url, options)
.then((response) => response.json())
.then((response) => console.log(response))
.catch((err) => console.error(err));
```
```bash cURL theme={null}
# Replace "staging" with "www" for production, and fill in your api key and mint action id
ENV="staging"
API_KEY=""
MINT_ACTION_ID=""
curl --header "x-api-key: $API_KEY" \
-X GET \
https://${ENV}.crossmint.com/api/2022-06-09/actions/${MINT_ACTION_ID}
```
Here is an example response from calling the status API:
```json JSON theme={null}
{
"actionId": "cace8d7d-e4dd-483d-a780-faf6aa9f761a",
"action": "nfts.create",
"status": "success",
"data": {
"chain": "solana",
"txId": "5KQHjcEq7DWnD1Rzo7RW7rknY3NFeKsJ9Lxkrd1DBPPwxbv9k4JAgxELWW5rqWMd5sXrHDwYzsRMeUok3tqYvTLa",
"collection": {...},
"recipient": {...},
"token": {...}
},
"startedAt": "2025-05-27T03:04:23.000Z",
"completedAt": "2025-05-27T03:04:49.000Z",
"resource": "https://staging.crossmint.com/api/2022-06-09/actions/cace8d7d-e4dd-483d-a780-faf6aa9f761a"
}
```
Pay attention to the "status" field. Once it says "success":
**Congratulations. You have minted your first NFT** 🥷 🎉
For scalable production applications, consider using [webhooks](/minting/nfts/integrate/webhooks-and-status-apis) to
determine when your NFT has been minted, instead of periodically polling for its status via the API.
### 1. Create a Developer Account
### 2. Get an API Key
Within the "Server-side keys" section, click the "Create new key" button in the top right.
Then, check the scopes `nfts.create` and `nfts.read` under the "Minting API"
category and create your key. Save this key for the next step.
### 3. Mint a Compressed NFT
We're almost there! With our key created, we're now going to write a small
function that creates a compressed NFT and delivers it to a user.
Compressed NFTs are a new standard on the Solana blockchain, for minting NFTs with the lowest cost amongst L1 and L2 blockchains, and the highest throughput (thousands of NFTs per second).
Create a file (e.g. `mintCompressedNFT.js`) and enter this code into it:
```javascript mintCompressedNFT.js theme={null}
const apiKey = "YOUR_API_KEY";
const chain = "solana";
const env = "staging"; // or "www"
const recipientEmail = "TEST_EMAIL_ADDRESS";
const recipientAddress = `email:${recipientEmail}:${chain}`;
const url = `https://${env}.crossmint.com/api/2022-06-09/collections/default-solana/nfts`;
const options = {
method: "POST",
headers: {
"accept": "application/json",
"content-type": "application/json",
"x-api-key": apiKey,
},
body: JSON.stringify({
recipient: recipientAddress,
metadata: {
name: "Crossmint Compressed NFT",
image: "https://picsum.photos/400",
description: "My first compressed NFT using Crossmint",
},
compressed: true,
reuploadLinkedFiles: false
}),
};
fetch(url, options)
.then((res) => res.json())
.then((json) => console.log(json))
.catch((err) => console.error("error:" + err));
```
This code creates a compressed NFT, uploads it to the Solana blockchain, and delivers it to a user wallet, attached to the email address provided. Before running it, be sure to fill in values for:
* `YOUR_API_KEY` with the key obtained in the prior step.
* `TEST_EMAIL_ADDRESS` with an email address you control, for testing.
Now, run the `mintCompressedNFT.js` script.
```bash theme={null}
node mintCompressedNFT.js
```
Current Protocol Limitations:
* There's a 10-30 second delay between when a compressed NFT is minted and it shows in wallets.
After a few seconds, it should return a response indicating the mint has
started processing. Check the full response and save its `actionId` property, as you'll use it later.
Here is an example response returned from the API call above:
```json JSON theme={null}
{
"actionId": "de26f73e-b0ae-4ac6-8784-39f20527d39d",
"onChain": {
"status": "pending",
"chain": "solana"
},
"id": "de26f73e-b0ae-4ac6-8784-39f20527d39d"
}
```
### 4. Confirm Delivery of the NFT
The mint has started processing. However, blockchains can take a few seconds (or,
at times of extreme network congestion, even minutes) to confirm the operation.
Before showing the user a success screen, the next step is checking the
status of the mint.
To do this, grab the `actionId` received at the end of step 3 and use it
alongside your API key in one of the snippets below.
```bash cURL theme={null}
# Replace "staging" with "www" for production, and fill in your api key and mint action id
ENV="staging"
API_KEY=""
MINT_ACTION_ID=""
curl --header "x-api-key: $API_KEY" \
-X GET \
https://${ENV}.crossmint.com/api/2022-06-09/actions/${MINT_ACTION_ID}
```
```javascript mintStatus.js theme={null}
const apiKey = "";
const env = "staging"; // or "www"
const actionId = "";
const url = `https://${env}.crossmint.com/api/2022-06-09/actions/${actionId}`;
const options = {
method: "GET",
headers: { "X-API-KEY": apiKey },
};
fetch(url, options)
.then((response) => response.json())
.then((response) => console.log(response))
.catch((err) => console.error(err));
```
Here is an example response from calling the status API:
```json JSON theme={null}
{
"actionId": "de26f73e-b0ae-4ac6-8784-39f20527d39d",
"action": "nfts.create",
"status": "success",
"data": {
"chain": "solana",
"txId": "dLFcyYtgFrrX7VkXh8U8JUmECNykH7VASzovNzM67NPRypCzMkTnx1ffv7dB99N4YrAT2mDRqszGofKunnazbXX",
"collection": {...},
"recipient": {...},
"token": {...}
},
"startedAt": "2025-05-27T03:09:47.000Z",
"completedAt": "2025-05-27T03:09:52.000Z",
"resource": "https://staging.crossmint.com/api/2022-06-09/actions/de26f73e-b0ae-4ac6-8784-39f20527d39d"
}
```
Pay attention to the "status" field. Once it says "success":
**Congratulations. You have minted your first compressed NFT** 🥷 🎉
Other explorers, like Solscan, still have not added support.
For scalable production applications, consider using [webhooks](/minting/nfts/integrate/webhooks-and-status-apis) to
determine when your NFT has been minted, instead of periodically polling for its status via the API.
## View your NFTs
1. If the NFTs were delivered to an **email address**, the recipient can see them by:
* Logging into their wallet from Crossmint's [website](https://www.crossmint.com). For staging, they must use [https://staging.crossmint.com](https://staging.crossmint.com).
* From your website if you use [embedded wallets](/wallets/overview). See the API for [getting the NFTs](/api-reference/wallets/get-nfts-from-wallet) in a wallet.
2. If the NFTs were delivered to a **wallet address**, the user will be able to see them there directly, connecting to testnet if needed, or on the testnet blockchain explorer.
And voilá, there's your NFT! Now think of all the cool things you can build
with this, at scale :)
## Launching in Production
For production, the steps are almost identical, but some changes are required:
1. Create a developer account on the [production console](https://www.crossmint.com/console).
2. Add credits to your account from [Billing & Usage](https://www.crossmint.com/console/billing).
3. Then, create a production key on the [API Keys](https://www.crossmint.com/console/projects/apiKeys) page with the
same API scopes.
4. Modify all code snippets with `const env = "www"`, so they use the production APIs. You may also need to change the
`chain` variable to match your production blockchain.
5. Check the guide with [best practices](/minting/advanced/best-practices)
## Learn More
Learn how to create and manage NFT collections.
Check out more advanced options for minting digital assets.
Update and delete tokens after minting.
# Semi-Fungible Tokens (SFTs)
Source: https://docs.crossmint.com/minting/quickstarts/sfts
Mint and send limited sets of identical digital assets in under 5 minutes
In this quickstart you will learn how to create semi-fungible tokens (SFTs) and deliver them to a wallet or email address.
## What are SFTs?
SFTs (semi-fungible tokens) follow the ERC-1155 standard. Each token is a replica of a predefined template. Each collection (smart contract) can contain multiple templates, which can contain many tokens.
This API only supports EVM chains self-serve. Contact us if you need support for another chain.
## Integration steps
### 1. Create a Developer Account
### 2. Get an API Key
Within the "Server-side keys" section, click the "Create new key" button in the top right.
Then, check the scopes `nfts.create`, `nfts.read`, and `collections.create` under the "Minting API"
category and create your key. Save this key for the next step.
### 3. Create an SFT collection
With our key created, we're now going to create an SFT collection to hold our templates.
```javascript createCollection.js theme={null}
const apiKey = "YOUR_API_KEY";
const env = "staging"; // or "www"
const url = `https://${env}.crossmint.com/api/2022-06-09/collections`;
const options = {
method: "POST",
headers: {
accept: "application/json",
"content-type": "application/json",
"x-api-key": apiKey,
},
body: JSON.stringify({
chain: "polygon-amoy",
fungibility: "semi-fungible",
metadata: {
name: "My SFT Collection",
imageUrl: "https://www.crossmint.com/assets/crossmint/logo.png",
description: "A new collection with its own dedicated smart contract",
},
}),
};
fetch(url, options)
.then((res) => res.json())
.then((json) => console.log(json))
.catch((err) => console.error("error:" + err));
```
```json JSON theme={null}
{
"id": "5263650e-6d43-4ed3-9e31-0cf593d076a4",
"metadata": {
"name": "My SFT Collection",
"description": "A new collection with its own dedicated smart contract",
"imageUrl": "https://www.crossmint.com/assets/crossmint/logo.png",
"symbol": "XMINT"
},
"fungibility": "semi-fungible",
"onChain": {
"chain": "polygon-amoy",
"type": "erc-1155"
},
"actionId": "5263650e-6d43-4ed3-9e31-0cf593d076a4"
}
```
Save the `id` from the response as your `COLLECTION_ID` for the next steps.
### 4. Create a template within that collection
Now, let's create a template within our collection:
```javascript createTemplate.js theme={null}
const apiKey = "YOUR_API_KEY";
const env = "staging"; // or "www"
const collectionId = "YOUR_COLLECTION_ID";
const url = `https://${env}.crossmint.com/api/2022-06-09/collections/${collectionId}/templates`;
const options = {
method: "POST",
headers: {
accept: "application/json",
"content-type": "application/json",
"x-api-key": apiKey,
},
body: JSON.stringify({
onChain: {
tokenId: "1",
},
supply: {
limit: 10,
},
metadata: {
name: "My template",
image: "https://www.crossmint.com/assets/crossmint/logo.png",
description: "A new token template for my ERC1155 collection",
},
}),
};
fetch(url, options)
.then((res) => res.json())
.then((json) => console.log(json))
.catch((err) => console.error("error:" + err));
```
```json JSON theme={null}
{
"templateId": "58b0c1aa-e457-48dd-bb55-5a27e6a92f74",
"metadata": {
"name": "My template",
"image": "ipfs://bafkreigbqsmxzkbjgbwtj6exfdt5z3t3swgoysf7hr6vjzddqnmykj6x2u",
"description": "A new token template for my ERC1155 collection"
},
"onChain": {
"tokenId": "1"
},
"supply": {
"limit": "10",
"minted": "0"
}
}
```
Save the `templateId` from the response for the next step.
### 5. Mint an SFT from a template
Now, let's mint an SFT from our template and send it to a wallet or email address:
```javascript mintSFT.js theme={null}
const apiKey = "YOUR_API_KEY";
const env = "staging"; // or "www"
const collectionId = "YOUR_COLLECTION_ID";
const templateId = "YOUR_TEMPLATE_ID";
const recipientEmail = "TEST_EMAIL_ADDRESS";
const url = `https://${env}.crossmint.com/api/2022-06-09/collections/${collectionId}/sfts`;
const options = {
method: "POST",
headers: {
accept: "application/json",
"content-type": "application/json",
"x-api-key": apiKey,
},
body: JSON.stringify({
templateId: templateId,
recipient: `email:${recipientEmail}:polygon-amoy`,
amount: 1,
}),
};
fetch(url, options)
.then((res) => res.json())
.then((json) => console.log(json))
.catch((err) => console.error("error:" + err));
```
```json JSON theme={null}
{
"actionId": "a91c15e3-60f2-4a45-bf1a-cee508981667",
"action": "nfts.create",
"status": "pending",
"data":
{
"chain": "polygon-amoy",
"collection":
{
"id": "84e3d617-9c1b-4e7a-9686-522a9ea7c520",
"contractAddress": "0x9b8ab8949bd7E73E61945b88F7fe12151f98ad3C"
},
"recipient":
{
"walletAddress": "0xcFDc00Cf926A5053f9Cdf004e6DF17e6dEB2E146",
"email": "test@example.com"
},
"token":
{
"id": "a91c15e3-60f2-4a45-bf1a-cee508981667"
}
},
"startedAt": "2024-01-02T22:05:01.000Z",
"resource": "https://staging.crossmint.com/api/2022-06-09/actions/a91c15e3-60f2-4a45-bf1a-cee508981667"
}
```
### 6. Confirm Delivery of the SFT
The mint has started processing. However, blockchains can take a few seconds (or,
at times of extreme network congestion, even minutes) to confirm the operation.
Before showing the user a success screen, the next step is checking the
status of the mint.
To do this, grab the `actionId` received at the end of step 5 and use it
alongside your API key in one of the snippets below.
```javascript mintStatus.js theme={null}
const apiKey = "";
const env = "staging"; // or "www"
const actionId = "";
const url = `https://${env}.crossmint.com/api/2022-06-09/actions/${actionId}`;
const options = {
method: "GET",
headers: { "X-API-KEY": apiKey },
};
fetch(url, options)
.then((response) => response.json())
.then((response) => console.log(response))
.catch((err) => console.error(err));
```
```bash cURL theme={null}
# Replace "staging" with "www" for production, and fill in your api key and mint action id
ENV="staging"
API_KEY=""
MINT_ACTION_ID=""
curl --header "x-api-key: $API_KEY" \
-X GET \
https://${ENV}.crossmint.com/api/2022-06-09/actions/${MINT_ACTION_ID}
```
Here is an example response from calling the status API:
```json JSON theme={null}
{
"actionId": "a91c15e3-60f2-4a45-bf1a-cee508981667",
"action": "nfts.create",
"status": "success",
"data": {
"collection": {},
"recipient": {},
"token": {}
},
"startedAt": "2024-01-02T22:05:01.000Z",
"completedAt": "2024-01-02T22:06:04.000Z",
"resource": "https://staging.crossmint.com/api/2022-06-09/actions/a91c15e3-60f2-4a45-bf1a-cee508981667"
}
```
Pay attention to the "status" field. Once it says "success":
**Congratulations. You have minted your first SFT** 🥷 🎉
For scalable production applications, consider using [webhooks](/minting/nfts/integrate/webhooks-and-status-apis) to
determine when your SFT has been minted, instead of periodically polling for its status via the API.
## View your SFTs
1. If the SFTs were delivered to an **email address**, the recipient can see them by:
* Logging into their wallet from Crossmint's [website](https://www.crossmint.com). For staging, they must use [https://staging.crossmint.com](https://staging.crossmint.com).
* From your website if you use [embedded wallets](/wallets/overview). See the API for [getting the NFTs](/api-reference/wallets/get-nfts-from-wallet) in a wallet.
2. If the SFTs were delivered to a **wallet address**, the user will be able to see them there directly, connecting to testnet if needed, or on the testnet blockchain explorer.
And voilá, there's your SFT! Now think of all the cool things you can build
with this, at scale :)
## Launching in Production
For production, the steps are almost identical, but some changes are required:
1. Create a developer account on the [production console](https://www.crossmint.com/console).
2. Add credits to your account from [Billing & Usage](https://www.crossmint.com/console/billing).
3. Then, create a production key on the [API Keys](https://www.crossmint.com/console/projects/apiKeys) page with the
same API scopes.
4. Modify all code snippets with `const env = "www"`, so they use the production APIs. You may also need to change the
`chain` variable to match your production blockchain.
5. Check the guide with [best practices](/minting/advanced/best-practices)
## Learn More
Learn how to create and manage SFT collections.
Check out more advanced options for minting.
Update and delete tokens after minting.
# Create Credential Templates
Source: https://docs.crossmint.com/minting/verifiable-credentials/integrate/create-credential-templates
Create Verifiable Credential templates with a single API call
A Verifiable Credential template is equivalent to an NFT collection. Similar to how a NFT collection groups NFTs together, a Verifiable Credential template references the credential type and additional credential configurations that credentials adhere to. Every Verifiable Credential must belong to a template.
When defining a template, the issuer initially specifies a **credentials** object. Within that the following needs to be provided:
* **type** (required): A credential's private data schema is referred to as "type". Types act as a protective measure, preventing the addition of unauthorized fields and, as a result, the tampering of the Verifiable Credential. You can [define your own](/minting/verifiable-credentials/integrate/create-credential-types).
* **encryption** (required): The method chosen to protect the credential's private data. If set to `none` the credential's private data will be stored in plain text. To encrypt and protect the credential's private data, read about the supported [encryption modalities](/minting/verifiable-credentials/integrate/encrypt-credentials).
* **storage** (required): The location where credentials defined by this template will be stored. Such storage can be on Crossmint, another company's database, or decentralized storage. Read about the supported [storage modalities](/minting/verifiable-credentials/integrate/store-credentials).
The template issuer must then specify the template's public metadata:
* **metadata** (required): A template's public name, description and imageUrl. Name is required, while the other attributes are optional.
Finally, the issuer provides information on:
* **chain** (required): The blockchain where the credential's contract and associated NFTs will be registered. Verifiable Credentials supports all EVM chains. Interested in launching your verifiable credentials on a specific blockchain? [Get in touch](https://www.crossmint.com/contact/sales).
* **delegatedSignature** (optional): A non-custodial wallet of choice to be used for signing credential issuance. By default, the issuer's Crossmint-managed custodial wallet will be used. Read more about delegated signatures [here](/minting/verifiable-credentials/integrate/delegated-signatures).
## 1. Create a Credential Template
To issue a Verifiable Credential you have to first create the template that this credential will adhere to. You can do so via a single API call (requires the API key scope `templates.create`):
```javascript createTemplate.js theme={null}
const myApiKey = ""; // Replace with key from step 2
const templateParams = {
credentials: {
type: "crossmint:5fe6040e-07a1-48bb-97a3-b588a7e927d2:CourseCompletionCertificate",
encryption: "none",
storage: "crossmint",
},
metadata: {
name: "Satoshi University Credentials",
description: "Credentials accredited by Satoshi University",
imageUrl: "https://picsum.photos/400",
},
chain: "polygon-amoy",
};
const options = {
method: "POST",
headers: {
"X-API-KEY": myApiKey,
"Content-Type": "application/json",
},
body: JSON.stringify(templateParams),
};
fetch("https://staging.crossmint.com/api/v1-alpha1/credentials/templates/", options)
.then((response) => response.json())
.then((response) => console.log(JSON.stringify(response)))
.catch((err) => console.error(err));
```
The template's metadata will be used by default for the NFT metadata, unless explicitly set when issuing a new credential.
## 2. Check the status of your template
It takes a few seconds (up to a minute, depending on the blockchain and how congested it is) to deploy a template.
To check the template's status, you will need the `nfts.create` API scope and run the following code.
```typescript checkTemplateStatus.js theme={null}
const templateId = "";
const options = { method: "GET", headers: { "X-API-KEY": "" } };
fetch(`https://staging.crossmint.com/api/2022-06-09/actions/${templateId}`, options)
.then((response) => response.json())
.then((response) => console.log(response))
.catch((err) => console.error(err));
```
Test any API in seconds directly from the docs.
Contact our sales team for support.
# Create Credential Types
Source: https://docs.crossmint.com/minting/verifiable-credentials/integrate/create-credential-types
Specify the schema that the Verifiable Credential adheres to
In the realm of Verifiable Credentials, each credential must be associated with a schema. The credential's schema is referred to as "type".
Credential types outline the attributes and their respective data types that the credential contains. The purpose of types is to ensure the credential's authenticity. Types allow you to specify the attributes you are interested in certifying and act as a protective measure, preventing the addition of unauthorized attributes and, as a result, the tampering of the Verifiable Credential.
It's important to note that all credentials within a single template will share the same type. Trying to issue or verify a credential with different field names or types will result in an error.
By adhering to this structure, we maintain the integrity and verifiability of the credentials, ensuring they serve their purpose effectively.
## Example Types
Credentials play a crucial role in verifying and validating various aspects of identity, achievement, and authorization. Below are some examples of credential types you can create and manage through Crossmint:
### Proof of Employment
* Purpose: Issued to individuals that want to prove their employment.
* Name: `ProofOfEmployment`
```typescript theme={null}
ProofOfEmployment {
position: string;
department: string;
startDate: string;
}
```
The payload used to create this type is the following:
```json theme={null}
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "Proof of Employment",
"description": "Schema for verifying employment details",
"type": "object",
"properties": {
"credentialSubject": {
"type": "object",
"properties": {
"position": {
"type": "string"
},
"department": {
"type": "string"
},
"startDate": {
"type": "string"
}
},
"required": ["position", "department", "startDate"],
"additionalProperties": false
}
}
}
```
To reference this example type in your credential template definition, use `crossmint:bfb292e7-2700-4924-9213-478f3d71f2d8:ProofOfEmployment`.
### Course Completion Certificates
* Purpose: Issued to students upon the successful completion of a course. The certificate may indicate that the student has mastered a specific skill or knowledge.
* Type: `CourseCompletionCertificate`
```typescript theme={null}
CourseCompletionCertificate {
course: string;
grade: string;
}
```
If you want to use this example type in your credential template definition, you can reference it as `crossmint:bfb292e7-2700-4924-9213-478f3d71f2d8:CourseCompletionCertificate`.
### Membership Credentials
* Purpose: Provided to individuals who are members of a specific organization or club. The certificate may grant access to exclusive resources and events.
* Name: `Membership`
```typescript theme={null}
Membership {
organizationName: string;
membershipType: string;
issueDate: string;
benefits: string[];
status: string;
}
```
If you want to use this example type in your credential template definition, you can reference it as `crossmint:bfb292e7-2700-4924-9213-478f3d71f2d8:Membership`.
### Event Participation Certificates
* Purpose: Issued to individuals who attend or participate in specific events.
* Name: `EventParticipation`
```typescript theme={null}
EventParticipation {
eventName: string;
eventDate: string;
location: string;
description: string;
}
```
If you want to use this example type in your credential template definition, you can reference it as `crossmint:bfb292e7-2700-4924-9213-478f3d71f2d8:EventParticipation`.
## Types Creation Quickstart
This example creates a custom type, `CourseCompletionCertificate`, for a credential that represents a course completion. The credential will contain two fields: `course` and `passed`.
```javascript createType.js theme={null}
const typeName = "CourseCompletionCertificate";
const schema = {
$schema: "https://json-schema.org/draft/2020-12/schema",
title: "Course completion",
description: "Describes the course completed and the assigned grade",
type: "object",
properties: {
credentialSubject: {
type: "object",
properties: {
course: {
type: "string",
},
grade: {
type: "string",
},
id: {
// Optional, will be added automatically
type: "string",
},
},
required: ["course", "grade"],
additionalProperties: false,
},
},
};
const options = {
method: "PUT",
headers: {
"X-API-KEY": "YOUR_API_KEY",
"Content-Type": "application/json",
},
body: JSON.stringify(schema),
};
fetch(`https://staging.crossmint.com/api/v1-alpha1/credentials/types/${typeName}`, options)
.then((response) => response.json())
.then((response) => console.log(JSON.stringify(response)))
.catch((err) => console.error(err));
```
Copy the `createType.js` file in the tab above, add your API key to the `X-API-KEY` header (or [create one](/minting/quickstarts/credentials#2-get-an-api-key) if you haven't already) and run via node.
```shell theme={null}
node createType.js
```
The response, shown below, includes the credential type name.
```json Response theme={null}
{
"id": "crossmint:bfb292e7-2700-4924-9213-478f3d71f2d8:CourseCompletionCertificate", // Internal type id
"typeSchema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "Course completion", // Veryfiable credential `type` name.
"description": "Describes the course completed and the assigned grade",
"type": "object",
"properties": {
"credentialSubject": {
"type": "object",
"properties": {
"course": {
"type": "string"
},
"grade": {
"type": "string"
},
"id": {
"type": "string"
}
},
"required": ["course", "grade"],
"additionalProperties": false
}
},
"$id": "https://staging.crossmint.com/api/v1-alpha1/credentials/types/crossmint:bfb292e7-2700-4924-9213-478f3d71f2d8:CourseCompletionCertificate" // External schema URI
}
}
```
The type id defined will be used when you create your [credential template](/minting/verifiable-credentials/integrate/create-credential-templates).
You can also utilize the `POST /credentials/types/` endpoint to generate a random uuid for the new credential type.
For internal use within Crossmint, reference the type using `id`. The `$id` will be included as the credentials
schema by Crossmint but should not be used to reference the type in Crossmint APIs.
Example of a credentials using the type `"crossmint:bfb292e7-2700-4924-9213-478f3d71f2d8:CourseCompletionCertificate"` just created.
```json Example Credential theme={null}
{
"id": "urn:uuid:820b09e8-bd6d-4680-b2b1-b564169fde8a",
"credentialSubject": {
"course": "Math",
"grade": "A",
"id": "did:polygon-amoy:0x1B887669437644aA348c518844660ef8d63bd643"
},
"nft": {
"tokenId": "7",
"chain": "polygon-amoy",
"contractAddress": "0xb14Bf8EEa68026a2746350059389Bd119CFB8E29"
},
"issuer": {
"id": "did:polygon-amoy:0xd9d8BA9D5956f78E02F4506940f42ac2dAB9DABd"
},
"type": [
"VerifiableCredential",
"Course completion" // Schema.title
],
"validFrom": "2024-11-19T18:41:30.503Z",
"@context": [
"https://www.w3.org/ns/credentials/v2"
],
"credentialSchema": {
"id": "https://staging.crossmint.com/api/v1-alpha1/credentials/types/crossmint:bfb292e7-2700-4924-9213-478f3d71f2d8:CourseCompletionCertificate", // Schema.$id
"type": "JsonSchema"
},
"proof": { ... }
}
```
## Types Creation Deep Dive
In the realm of Verifiable Credentials, types are defined using the JSON schema. For more details, refer to the [W3C VC JSON Schema](https://www.w3.org/TR/vc-json-schema/).
When calling the create type API an invalid JSON schema will result in an error.
### Type schema structure
Below is an example of a JSON schema structure for defining a credential type:
```json Example Schema Payload theme={null}
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "Example nested schema",
"description": "A schema capturing a human name and a nested `additionalInfo` object",
"type": "object",
"properties": {
"credentialSubject": {
"type": "object",
"properties": {
"firstName": {
"type": "string"
},
"id": {
// This will be added automatically, is not necessary
"type": "string"
},
"additionalInfo": {
"type": "object",
"properties": {
"age": {
"type": "integer"
},
"isActive": {
"type": "boolean"
},
"tags": {
// Optional, therefore is not added to `required`
"type": "array",
"items": {
"type": "string"
}
}
},
"required": ["age", "isActive"],
"additionalProperties": false // Needs to be repeated for each nested object
}
},
"required": ["firstName", "additionalInfo"],
"additionalProperties": false
}
}
}
```
#### Explanation of Each Part:
* **\$schema**: This should always be set to "[https://json-schema.org/draft/2020-12/schema](https://json-schema.org/draft/2020-12/schema)".
* **\$id**: This field should NOT be defined, will be generated by Crossmint and returned in the response.
* **title**: Represents the name of the type.
* **description**: A brief description of the schema.
* **type**: Must be set to "object".
* **properties**: Must include `credentialSubject` as an object.
#### Important Notes:
* The credential subject is the section that needs to be defined. Any field or nested field can be added, as long as it is valid JSON schema.
* The `credentialSubject.properties.id` will be automatically added with the type "string", this is used to specify the wallet and therefore the identity of the subject.
* `additionalProperties` should always be set to `false` to prevent unauthorized fields from being added.
* All specified fields should be marked as `required` unless they are optional. This ensures that missing fields will trigger an error during schema validation.
* `additionalProperties` and `required` apply only at the level they are specified. For nested objects, these need to be repeated.
### Type import
If you already have a JSON schema type, you can easily import it by calling the same endpoint with the following structure:
```javascript importType.js theme={null}
const payload = {
$id: "https://raw.githubusercontent.com/decentralized-identity/credential-schemas/main/schemas/ExampleEntity/ExampleNameSchema.json",
};
const options = {
method: "POST",
headers: {
"X-API-KEY": "YOUR_API_KEY",
"Content-Type": "application/json",
},
body: JSON.stringify(schema),
};
fetch(`https://staging.crossmint.com/api/v1-alpha1/credentials/types`, options)
.then((response) => response.json())
.then((response) => console.log(JSON.stringify(response)))
.catch((err) => console.error(err));
```
```json Response theme={null}
{
"id": "crossmint:e62564a7-06eb-4f65-b389-eb3b7a4f6f98:10ede8a6",
"typeSchema": {
"$id": "https://raw.githubusercontent.com/decentralized-identity/credential-schemas/main/schemas/ExampleEntity/ExampleNameSchema.json",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "Example Name schema",
"description": "A schema capturing a human name",
"type": "object",
"properties": {
"credentialSubject": {
"type": "object",
"properties": {
"firstName": {
"type": "string"
},
"id": {
"type": "string"
}
},
"required": ["firstName"],
"additionalProperties": false
}
}
}
}
```
#### Important Notes for Import:
* All notes for type creation apply here as well.
* `credentialSubject.properties.id` will not be added for imported schemas. If the original schema does not include this specification, the resulting credentials will not include the user wallet in the `subject.id`. To know the subject of a credential, checking the NFT owner will be necessary.
To follow along and test the API calls use a tool like Postman or our [API
playground](/api-reference/verifiable-credentials/types/create-named-type).
Test any API in seconds directly from the docs.
Contact our sales team for support.
# Decrypt Credentials
Source: https://docs.crossmint.com/minting/verifiable-credentials/integrate/decrypt-credentials
Decrypt credentials you care about
As a credential issuer you can use the [retrieval endpoints](/minting/verifiable-credentials/integrate/retrieve-credentials) and Crossmint will autodecrypt the credential for you.
As a verifier, however, working with [encryption modalities](/minting/verifiable-credentials/integrate/encrypt-credentials) to decrypt credentials requires deep understanding of their APIs, managing cryptographic operations securely, and handling user signature interactions.
The Verifiable Credentials SDK encapsulates all this functionality for verifiers, ensuring secure and seamless decryption.
* Upon a verifier's request, the SDK prompts the credential subject to sign a message proving they are the credential subject, and approving the credential's decryption.
* Once the subject approves, Crossmint decrypts the credential for the verifier to verify.
If the credentials are encrypted using the Lit protocol, in order to decrypt them, the SDK provides a `Lit.decrypt(encryptedCredential)` function.
If the credentials are encrypted using Crossmint, the SDK provides a `CrossmintMetamaskDecrypt().decrypt(encryptedCredential)` function that decrypts the credential, assuming the user has a Metamask wallet. For the support of additional wallets during decryption, review our SDK reference.
A Crossmint API key (`credentials.decrypt`) is needed when:
* Crossmint has encrypted the data (`crossmint-recoverable` encryption modality)
* Data is encrypted via Lit but the verifier wants to delegate Lit network billing to Crossmint
Decryption will not work server-side when using Lit.
In both cases, the user will be prompted to sign a message with their wallet to authenticate the decryption, then the credential will be decrypted.
```typescript decrypt.ts theme={null}
import * as sdk from '@crossmint/client-sdk-verifiable-credentials';
sdk.CrossmintAPI.init('YOUR_API_KEY');
const encryptedCredential = ;
if (collection.metadata.credentialMetadata.encryption.type==VerifiableCredentialEncryptionType.DECENTRALIZED_LIT){
const decryptedData = await new sdk.Lit("staging").decrypt(encryptedCredential);
}else if (collection.metadata.credentialMetadata.encryption.type==VerifiableCredentialEncryptionType.CROSSMINT_RECOVERABLE){
const decryptedData = await new sdk.CrossmintMetamaskDecrypt().decrypt(encryptedCredential);
} else {
throw new Error("Not supported")
}
console.log("Decrypted credential:", decryptedData);
```
```json Response theme={null}
{
"unencryptedCredential": {
"id": "urn:uuid:4c9c41af-8410-4265-9fe4-3674d74e80d9",
"credentialSubject": {
"course": "Blockchain 101",
"grade": "A",
"id": "did:polygon-amoy:0xbFB0d0F9d49d80062103199c5aD309CE7a808039"
},
"validUntil": "2034-02-02",
"nft": {
"tokenId": "1",
"chain": "polygon-amoy",
"contractAddress": "0x288AAbb7D72A041Ea10719d2d676B65D2E9689F5"
},
"issuer": { "id": "did:polygon-amoy:0xB658f974Fe3744A6F9344810BC88e021E31B4d3e" },
"type": [
"VerifiableCredential",
"crossmint:5fe6040e-07a1-48bb-97a3-b588a7e927d2:courseCompletionQuickstart"
],
"validFrom": "2024-09-14T13:59:34.918Z",
"@context": [
"https://www.w3.org/2018/credentials/v1"
],
"proof": {
"verificationMethod": "did:polygon-amoy:0xB658f974Fe3744A6F9344810BC88e021E31B4d3e#evmAddress",
"created": "2024-09-14T13:59:34.918Z",
"proofPurpose": "assertionMethod",
"type": "EthereumEip712Signature2021",
"proofValue": "0x80587c5e9a801f4eed5bca95c48083277fc2ffe2fc1254f230f1b8c11cab59f63afaaa29bf29007129068243feb0069c46a8e583a64e64ff2517a2e3babd18101b",
"eip712": {
"domain": {
"name": "Crossmint",
"version": "0.1",
"chainId": 4,
"verifyingContract": "0xD8393a735e8b7B6E199db9A537cf27C61Aa74954"
},
"types": {
"VerifiableCredential": [
{ "name": "@context", "type": "string[]" },
{ "name": "type", "type": "string[]" },
{ "name": "id", "type": "string" },
{ "name": "issuer", "type": "Issuer" },
{ "name": "credentialSubject", "type": "CredentialSubject" },
{ "name": "validFrom", "type": "string" },
{ "name": "validUntil", "type": "string" },
{ "name": "nft", "type": "Nft" }
],
"CredentialSubject": [
{ "name": "id", "type": "string" },
{ "name": "course", "type": "string" },
{ "name": "grade", "type": "string" }
],
"Issuer": [
{ "name": "id", "type": "string" }
],
"Nft": [
{ "name": "tokenId", "type": "string" },
{ "name": "contractAddress", "type": "string" },
{ "name": "chain", "type": "string" }
]
},
"primaryType": "VerifiableCredential"
}
}
}
}
```
The decrypted credential content can now be reviewed and verified by the verifier.
# Delegated Signature
Source: https://docs.crossmint.com/minting/verifiable-credentials/integrate/delegated-signatures
Sign credentials with your wallet of choice
Credential issuers, by default, sign credentials using their custodial wallet, managed by Crossmint. This means that Crossmint handles the management of the wallet used for signing, providing a default and streamlined solution for issuers.
If an issuer wants to sign credentials using a separate wallet they own, they can do so by providing a signing endpoint. This endpoint is essentially a service or URL that the issuer can set up, which Crossmint will use to access the issuer's chosen wallet for signing credentials.
Delegated signature and encryption are not concurrently supported for the moment, so it is advised to also use
[delegated storage](/minting/verifiable-credentials/integrate/store-credentials#delegated-storage) for sensitive
data.
## Template Creation
Upon template creation, customers can specify parameters that configure the `delegatedSignature` specification:
* `issuerDid`: The desired issuer DID represents the entity that will be issuing the credentials.
* `endpoint`: The signing endpoint is the URL or endpoint where the credential signing process takes place
* `token`: The Auth token for the endpoint is a secure token used to authenticate requests to the signing endpoint. This token ensures that only authorized entities can access the signing endpoint.
```json theme={null}
{
...standard_template_params,
delegatedSignature: {
issuerDid: "did:polygon:<0xADDRESS>",
endpoint: "string",
token: "string"
}
}
```
The credential issuance process remains unchanged, except for one crucial modification. Instead of directly calling `wallet.signTypedData(payload)` using Crossmint's wallet, Crossmint sends the payload to a designated endpoint. This endpoint is responsible for wrapping the `signTypedData` call and performing the necessary security checks to ensure only authorized payloads are processed, preventing arbitrary or malicious data from being signed.
## Request Payload
Crossmint will POST to the given endpoint with the following payload:
```json theme={null}
{
"issuer": "did:polygon:<0xADDRESS>",
"domain": "ethers.TypedDataDomain",
"types": "Record>",
"message": "",
"token": ""
}
```
## Response Format
The endpoint should return a response with the following format:
```json theme={null}
{
"credential": "",
"issuer": "",
"signature": "string"
}
```
## DID
The issuer's identity is represented by a [DID](https://www.w3.org/TR/did-core/). The DID is a string that uniquely identifies the issuer.
The easiest way to provide a DID is to send the issuer's wallet address as DID, setting issuerDid to `"did:{chain}:{address}"`.
### Web DID
Crossmint also supports [Web DID](https://w3c-ccg.github.io/did-method-web/). This allows issuer's to be identified by a domain name, setting issuerDid to `"did:web:issuer.com"`. webDID leverages existing web domain reputation to establish trust around decentralized identities.
The issuer must have a [DID Document](https://www.w3.org/TR/did-core/#did-documents) hosted at `https://issuer.com/.well-known/did.json` for the webDID mapping to work properly.
The DID document, among other useful information, needs to contain the "service" attribute, stating the issuer's wallet address:
The issuer's wallet is the wallet that will be used to sign the credential and verifiers can recognize this wallet by its related webDID.
```json theme={null}
{
"id": "did:web:myissuer.com",
"service": [
{
"id": "did:web:myissuer.com#wallet",
"type": "wallet",
"serviceEndpoint": "chain:<0xADDRESS>"
}
]
// ... other properties like verificationMethod, authentication, etc.
}
```
# Credential Encryption
Source: https://docs.crossmint.com/minting/verifiable-credentials/integrate/encrypt-credentials
Encrypt private credential data
Verifiable Credentials can be used to store sensitive information. In such cases, it's crucial to use encrypted credentials to ensure data privacy and security.
The encryption process is designed so that *only* the credential subject and the credential issuer can decrypt the credential. This ensures that sensitive information is only accessible to the relevant parties.
Once the credential is decrypted, it can be verified by anyone. This allows for the credential's authenticity to be confirmed while still maintaining the privacy of its content.
The process of encryption and decryption can be automated using the Crossmint API and SDK. This provides a seamless and secure way to handle sensitive information within Verifiable Credentials.
Encryption will make credential issuance and retrieval slower due to the encryption and decryption process.
## Create an encrypted credential template
When creating a [template](/minting/verifiable-credentials/integrate/create-credential-templates), you can specify an `encryption` parameter to determine the encryption modality you will use. The possible values for this parameter are:
* `crossmint-recoverable`: This option is ideal when users prioritize the convenience and security of having a fallback recovery mechanism. Crossmint will take care of encrypting the credential. The user will be required to prove ownership of the subject wallet to decrypt the credential. As a fallback mechanism, the key is also shared with the credential issuer.
* `decentralized-lit`: This option is better suited for users who prioritize full decentralization and privacy. The credential is encrypted and decrypted using the [Lit protocol](https://www.litprotocol.com/). The user will be required to prove ownership of the subject wallet to decrypt the credential.
* `none`: The certificate is not encrypted.
For example, the `encryption` field part of the template creation request below is set to `crossmint-recoverable`.
```json theme={null}
{
"metadata": {
"name": "Template Name",
"description": "Encrypted credentials template"
},
"chain": "polygon",
"credentials": {
"type": "MyCustomType",
"encryption": "crossmint-recoverable"
}
}
```
During credential creation, the Crossmint API encrypts the credential and sets an access rule that only the credential subject and the credential issuer can decrypt the credential.
Credentials issued from this template will be encrypted for you. Behind the scenes:
* The Crossmint API encrypts the certificate's private data and sets an access rule that only the credential subject and issuer can decrypt the credential.
* Crossmint triggers a webhook to the issuer after a successful credential creation.
* Issuer receives via webhook the credential content and the encryption key.
* As a fallback a copy of the encryption key is sent to the issuer.
## Encrypted credential object
An encrypted credential consists of a 'credentialId' and a base64 encoded encrypted payload.
```json theme={null}
{
"credentialId": "urn:uuid:",
"payload": "base64_encoded_cipher_text"
}
```
## Retrieve an encrypted credential
You can leverage the standard retrieval Crossmint API endpoint to retrieve an encrypted credential:
`GET https://staging.crossmint.com/api/v1-alpha1/credentials/{credentialId}`
Hitting the `GET credentials/{credentialId}` endpoint as the issuer will automatically decrypt the credential for
you and return both the clear and ciphertext.
In case of successful autodecryption, the response object will be:
```json theme={null}
{
"encryptedCredential": {
"credentialId": "urn:uuid:",
"payload": "base64_encoded_cipher_text"
},
"unencryptedCredential": "",
"decryptionError": undefined
}
```
For information on how to decrypt a credential, see [here](/minting/verifiable-credentials/integrate/decrypt-credentials).
# Issue Credentials
Source: https://docs.crossmint.com/minting/verifiable-credentials/integrate/issue-credentials
Specify the credential's values and a recipient to send it to
With both a credential type and template defined, we can proceed to issue a credential.
## 1. Issue a Credential
First and foremost, the issuer must specify a valid recipient for the credential:
* **recipient** (required): Issuer must specify a credential recipient. This can be done via `email:${userEmail}:${chain}` or `{wallet address}`.
In addition, the issuer must specify a **credential** object containing:
* **subject** (required): Object matching a defined credential type's schema, with values relevant to the recipient. The credential's contents are identified by the "subject" key within the credential, following [W3C naming standards](https://www.w3.org/TR/vc-data-model-2.0/#credential-subject).
The credential subject (credential.subject) must respect the schema of the chosen Verifiable Credential type. You
cannot add additional fields, nor exclude any that were previously set.
* **expiresAt** (required): Specify date, in form such as `2034-02-02` (ISO) or `none` to imply infinite expiration.
If you don't include an expiration date, you must revoke the credential via burning to invalidate it.
Finally, the issuer can provide optional public metadata related to the specific credential, as such:
* **metadata** (optional): NFT metadata is inherited from the VC template metadata. Metadata attributes can be set by the customer to override metadata definition inherited by baseURI.
To issue your first credential, copy the `issueCredential.js` file from below, add your API key, and templateId (that was returned to you in the previous step), and run the file from your terminal.
```javascript issueCredential.js theme={null}
const userEmail = "user@email.com"; // Replace with recipient email
const templateId = "YOUR_TEMPLATE_ID"; // Replace with ID from previous step
const credentialParams = {
recipient: `email:${userEmail}:polygon-amoy`,
credential: {
// The CourseCompletionCertificate credential type requires course and grade declarations
subject: {
course: "Blockchain 101",
grade: "A",
},
expiresAt: "2034-02-02",
},
};
const options = {
method: "POST",
headers: {
"X-API-KEY": "YOUR_API_KEY",
"Content-Type": "application/json",
},
body: JSON.stringify(credentialParams),
};
fetch(`https://staging.crossmint.com/api/v1-alpha1/credentials/templates/${templateId}/vcs`, options)
.then((response) => response.json())
.then((response) => console.log(JSON.stringify(response)))
.catch((err) => console.error(err));
```
```json Response theme={null}
{
"id": "d7eb777b-e9b4-4f34-ab5f-ce199111166a",
"onChain": {
"status": "pending",
"chain": "polygon-amoy",
"contractAddress": "0xdC444A3F4768185497Dae6250E2F348b99bE89F3"
},
"credentialId": "urn:uuid:64f9877d-a19a-4205-8d61-f8c2abed5766",
"actionId": "d7eb777b-e9b4-4f34-ab5f-ce199111166a"
}
```
```shell theme={null}
node issueCredential.js
```
## 2. Check the status of your credential
You can [set up a webhook](/minting/verifiable-credentials/integrate/webhooks) to know when the Verifiable Credential NFT minting is completed, or call the
[action status API](/api-reference/common/get-action-status) with the returned `actionId`.
To check the template's status you will need the `nfts.create` API scope and run the following code.
```typescript checkTemplateStatus.js theme={null}
const actionId = ""; // Returned from previous step
const options = { method: "GET", headers: { "X-API-KEY": "" } };
fetch(`https://staging.crossmint.com/api/2022-06-09/actions/${actionId}`, options)
.then((response) => response.json())
.then((response) => console.log(response))
.catch((err) => console.error(err));
```
Test any API in seconds directly from the docs.
Contact our sales team for support.
# Retrieve Credentials
Source: https://docs.crossmint.com/minting/verifiable-credentials/integrate/retrieve-credentials
Get access to credentials you care about
Retrieving and effectively presenting credentials is essential for issuers, verifiers, and credential subjects. However, doing so requires interfacing with the blockchain, 3rd party services to fetch NFTs, all while at the same time struggling with unpredictable network issues, and data inconsistency.
Crossmint’s Verifiable Credentials API and SDK simplify credential management, allowing you to seamlessly retrieve and use verifiable credentials where they matter most.
## Verifiable Credential API
Crossmint's Verifiable Credentials API is primarily meant to be used by issuers. It's designed for robust backend operations, enabling seamless, automated credential retrieval without requiring user interaction.
The API integrates with your existing server-side infrastructure, ensuring efficient and scalable credential management. With the API, you can implement complex business logic, providing unparalleled control and customization.
You can retrieve a Verifiable Credential via the Crossmint API using different identifiers associated with the credential itself, or the NFT associated with the credential.
* [Get Verifiable Credential by ID](/api-reference/verifiable-credentials/credentials/retrieve-credential-by-id) uses the format: `urn:uuid:`
* [Get Verifiable Credential by NFT Locator](/api-reference/verifiable-credentials/credentials/retrieve-credential-by-nft-locator) uses the format: `::`
* [Get Verifiable Credential by NFT ID](/api-reference/verifiable-credentials/credentials/retrieve-credential-by-nft) uses crossmint's internal NFT ID
There is no access control on credential retrieval. Credential data is public and can be retrieved by anyone. Choose
to [encrypt your credentials](/minting/verifiable-credentials/integrate/encrypt-credentials) if you need to protect
your data.
Credential retrieval via the crossmint API will not work for [delegated
storage](/minting/verifiable-credentials/integrate/store-credentials).
To retrieve the credential, copy the `retrieveCredential.js` file from below, use your API key, the `credentialId`, and run the file from your terminal.
```javascript retrieveCredential.js theme={null}
const options = {
method: "GET",
headers: {
"X-API-KEY": "YOUR_API_KEY",
},
};
fetch(`https://staging.crossmint.com/api/v1-alpha1/credentials/${credentialId}`, options)
.then((response) => response.json())
.then((response) => console.log(JSON.stringify(response)))
.catch((err) => console.error(err));
```
```json Response theme={null}
{
"unencryptedCredential": {
"id": "urn:uuid:4c9c41af-8410-4265-9fe4-3674d74e80d9",
"credentialSubject": {
"course": "Blockchain 101",
"grade": "A",
"id": "did:polygon-amoy:0xbFB0d0F9d49d80062103199c5aD309CE7a808039"
},
"validUntil": "2034-02-02",
"nft": {
"tokenId": "1",
"chain": "polygon-amoy",
"contractAddress": "0x288AAbb7D72A041Ea10719d2d676B65D2E9689F5"
},
"issuer": { "id": "did:polygon-amoy:0xB658f974Fe3744A6F9344810BC88e021E31B4d3e" },
"type": ["VerifiableCredential", "crossmint:5fe6040e-07a1-48bb-97a3-b588a7e927d2:courseCompletionQuickstart"],
"validFrom": "2024-09-14T13:59:34.918Z",
"@context": ["https://www.w3.org/2018/credentials/v1"],
"proof": {
"verificationMethod": "did:polygon-amoy:0xB658f974Fe3744A6F9344810BC88e021E31B4d3e#evmAddress",
"created": "2024-09-14T13:59:34.918Z",
"proofPurpose": "assertionMethod",
"type": "EthereumEip712Signature2021",
"proofValue": "0x80587c5e9a801f4eed5bca95c48083277fc2ffe2fc1254f230f1b8c11cab59f63afaaa29bf29007129068243feb0069c46a8e583a64e64ff2517a2e3babd18101b",
"eip712": {
"domain": {
"name": "Crossmint",
"version": "0.1",
"chainId": 4,
"verifyingContract": "0xD8393a735e8b7B6E199db9A537cf27C61Aa74954"
},
"types": {
"VerifiableCredential": [
{ "name": "@context", "type": "string[]" },
{ "name": "type", "type": "string[]" },
{ "name": "id", "type": "string" },
{ "name": "issuer", "type": "Issuer" },
{ "name": "credentialSubject", "type": "CredentialSubject" },
{ "name": "validFrom", "type": "string" },
{ "name": "validUntil", "type": "string" },
{ "name": "nft", "type": "Nft" }
],
"CredentialSubject": [
{ "name": "id", "type": "string" },
{ "name": "course", "type": "string" },
{ "name": "grade", "type": "string" }
],
"Issuer": [{ "name": "id", "type": "string" }],
"Nft": [
{ "name": "tokenId", "type": "string" },
{ "name": "contractAddress", "type": "string" },
{ "name": "chain", "type": "string" }
]
},
"primaryType": "VerifiableCredential"
}
}
}
}
```
```shell theme={null}
node retrieveCredential.js
```
## Verifiable Credentials SDK
Crossmint's Verifiable Credentials SDK is primarily meant to be used by verifiers. Using the Verifiable Credentials client-side SDK is advantageous in scenarios where direct interaction with the user's wallet is preferred, directly integrating their credentials with your browser-based application.
* **Wallet Integration**: The Verifiable Credentials SDK is designed to work smoothly with any wallet, providing a streamlined experience for both users and verifiers. This reduces friction and improves the user experience.
* **Efficient Retrieval**: Once the wallet is connected, the SDK can directly interact with it to retrieve credentials. Instead of dealing with multiple API calls, the SDK surfaces credential retrieval capabilities that matter most to verifiers, such as fetching credentials based on specific filter conditions, i.e. type and issuer.
* **User Approvals**: The SDK takes care of prompting the user, when applicable, to approve transactions, such as decryption requests, enhancing security and the user's confidence in the application.
Using the SDK requires a Crossmint developer account and a client-side API key scoped to `credentials.read` and `credentials.decrypt`. Go to the `Developers -> API Keys` tab, click on `New API Key` in the `Client-side keys` section, and select the API key scopes.
You can download the SDK [here](https://www.npmjs.com/package/@crossmint/client-sdk-verifiable-credentials).
The SDKs offer functions to easily retrieve and filter NFTs associated with credentials. For example, the `getCredentialNFTs` function fetches all VC NFTs in a wallet and lets you filter by credential type or issuer.
Below, we show how easy it is to retrieve academic credentials from a user's wallet.
```typescript index.ts theme={null}
import * as sdk from '@crossmint/client-sdk-verifiable-credentials';
sdk.CrossmintAPI.init('YOUR_API_KEY');
const wallet = "USER_WALLET";
const filters = {
// typeId represents an academic credential schema
type:
};
const credentials = await sdk.getCredentialNFTs(
"polygon",
wallet,
filters
);
console.log('Academic credentials retrieved successfully:', credentials);
```
```json Response theme={null}
[
{
"contractAddress": "0x5E1ab2aC27f1a0AD28f1b0DDb0E00baF2677725e",
"chain": "polygon-amoy",
"nfts": [
{
"chain": "polygon-amoy",
"contractAddress": "0x5E1ab2aC27f1a0AD28f1b0DDb0E00baF2677725e",
"tokenId": "1"
}
],
"metadata": {
"name": "Crossmint Tickets",
"description": "Access Crossmint Events",
"image": "ipfs://Qme551yKU6a5Cptsk8hxcMtWwTrKkHArZtTTwFSvu19eFD",
"credentialMetadata": {
"type": [
"VerifiableCredential",
"userName"
],
"encryption": {
"type": "crossmint-recoverable"
},
"credentialsEndpoint": "ipfs://bafybeidmfu6xmdpxddads6nsyxfg6hd5ylj2drrywhzvfi2bbq45owklsa",
"issuerDid": "did:polygon:0xd9d8BA9D5956f78E02F4506940f42ac2dAB9DABd"
}
}
},
{
"contractAddress": "0x38989746BF435d5f6Dc7C8DdE6038b5a17E62cE2",
"chain": "polygon-amoy",
"nfts": [
{
"chain": "polygon-amoy",
"contractAddress": "0x38989746BF435d5f6Dc7C8DdE6038b5a17E62cE2",
"tokenId": "1"
}
],
"metadata": {
"name": "Crossmint Tickets",
"description": "Access Crossmint Events",
"image": "ipfs://Qme551yKU6a5Cptsk8hxcMtWwTrKkHArZtTTwFSvu19eFD",
"credentialMetadata": {
"type": [
"VerifiableCredential",
"userName"
],
"encryption": {
"type": "crossmint-recoverable"
},
"credentialsEndpoint": "a",
"issuerDid": "did:polygon:0xd9d8BA9D5956f78E02F4506940f42ac2dAB9DABd"
}
}
}
]
```
Now that the credential is retrieved, it's important to decrypt it and verify it.
In case the endpoint is called by the issuer the credential will be automatically decrypted.
Test any API in seconds directly from the docs.
Contact our sales team for support.
# Revoke Credentials
Source: https://docs.crossmint.com/minting/verifiable-credentials/integrate/revoke-credentials
Invalidate a credential, rendering it no longer trustworthy or verifiable
To revoke a credential, you can directly use the [revoke credential API](/api-reference/verifiable-credentials/credentials/revoke-credential). Burning the NFT associated with the credential via the [burn-nft](/api-reference/minting/nfts/burn-nft) API accomplishes the same result.
Issuers can revoke a Verifiable Credential without the subject's intermediation.
To revoke the credential, copy the `revokeCredential.js` file from below, use your API key, the credentialId, and run the file from your terminal.
```javascript revokeCredential.js theme={null}
const options = {
method: "DELETE",
headers: {
"X-API-KEY": "YOUR_API_KEY",
},
};
fetch(`https://staging.crossmint.com/api/v1-alpha1/credentials/${credentialId}`, options)
.then((response) => response.json())
.then((response) => console.log(JSON.stringify(response)))
.catch((err) => console.error(err));
```
```shell theme={null}
node revokeCredential.js
```
Test any API in seconds directly from the docs.
Contact our sales team for support.
# Credential Storage
Source: https://docs.crossmint.com/minting/verifiable-credentials/integrate/store-credentials
Choose how to store credential data
When creating a [template](/minting/verifiable-credentials/integrate/create-credential-templates), you can specify a `storage` parameter to determine where the credential payload will be stored. The possible values for this parameter are:
* `crossmint`
* `crossmint-private`
* `delegated`
* `decentralized-ipfs`
## Crossmint
With this option, credentials are stored on the Crossmint database. This is the most flexible solution and is compatible with all other features.
There is not access control on credential retrieval, credential data is public and can be retrieved by anyone, use
[encrypted credentials](/minting/verifiable-credentials/integrate/encrypt-credentials) if you need to protect the
data.
## Crossmint Private
This option is designed for integrated systems in which the issuer is also the verifier. Credentials are stored by Crossmint, but only the issuer can retrieve the credential payload. Because the credential issuer is also the credential verifier, there is no need to encrypt the credentials.
## Decentralized Storage
This option is the most open and transparent solution as it will store the credential to a decentralized storage (IPFS). All retrieval API endpoints will still be available and will proxy the request to ipfs.
[Encrypting the payload](/minting/verifiable-credentials/integrate/encrypt-credentials) is suggested to avoid private credential data being exposed to be public.
Credential issuance will be slower than other storage options.
## Delegated Storage
This option is designed for the enterprise, giving the issuer full control over the credentials. With delegated storage, no reference to the data is stored on Crossmint databases. The credential is returned to the issuer via the [webhook](/minting/verifiable-credentials/integrate/webhooks) and the issuer is responsible for storing it.
All Crossmint retrieval endpoints will consequently be disabled. A `delegatedStorageEndpoint` parameter must be added when creating the credential template that specifies the endpoint where the credential can be retrieved (can be set to a dummy value like "unknown" if not desired).
This endpoint will be saved in the contract metadata and can be used by users to retrieve the credential associated with their NFT. Issuers can protect content privacy by encrypting the payload.
For delegated storage, if the credential is not catched by the webhook, it will be lost. Read more about webhooks
[here](/minting/verifiable-credentials/integrate/webhooks).
# Verify Credentials
Source: https://docs.crossmint.com/minting/verifiable-credentials/integrate/verify-credentials
Ensure that the credential is valid
Verification is a crucial step in the process of handling Verifiable Credentials. To ensure a credential is valid, the following checks are performed:
* Credential Structure: The credential should not be malformed. It must adhere to the correct structure as defined by the [credential type](/minting/verifiable-credentials/integrate/create-credential-types) .
* Signature Validation: The credential’s signature must be valid and match the identity of the issuer. This ensures that the credential was indeed issued by the claimed issuer and hasn’t been tampered with.
* Attribute Check: No additional attributes should have been added to the credential beyond what’s defined in its schema. This prevents unauthorized modifications to the credential.
* Expiration Check: The credential should not have expired. Credentials often have a validity period after which they are no longer considered valid.
* Revocation Check: The credential should not have been revoked by the issuer. Even if a credential is valid and hasn’t expired, it may have been revoked by the issuer for various reasons.
By performing these checks, we can ensure the authenticity and validity of a Verifiable Credential. Verifying a credential can happen either via the Crossmint API or SDK.
## API
To verify a credential on the server-side use the Crossmint API and the following endpoint, passing the [retrieved](/minting/verifiable-credentials/integrate/retrieve-credentials) unencrypted credential JSON.
```javascript verifyCredential.js theme={null}
const options = {
method: "POST",
headers: {
"X-API-KEY": "YOUR_API_KEY",
"Content-Type": "application/json",
},
body: `{"credential": ${JSON.stringify(credential.unencryptedCredential)}}`,
};
fetch("https://staging.crossmint.com/api/v1-alpha1/credentials/verification/verify", options)
.then((response) => response.json())
.then((response) => console.log(JSON.stringify(response)))
.catch((err) => console.error(err));
```
```json Response theme={null}
{
"isValid": true
}
```
Verifying a credential via the API will require a Crossmint developer account.
## SDK
With the Verifiable Credentials client-side SDK, the verification process is performed locally, eliminating the need for interaction with Crossmint. Consequently, there is no need for a Crossmint developer account to verify the credential.
Install the `@crossmint/client-sdk-verifiable-credentials` [npm package](https://www.npmjs.com/package/@crossmint/client-sdk-verifiable-credentials) and call the `verifyCredential` function passing a `VerifiableCredential` object, as shown below.
```typescript decrypt.ts theme={null}
import * as sdk from '@crossmint/client-sdk-verifiable-credentials';
sdk.CrossmintAPI.init('YOUR_API_KEY');
const decryptedCredential = ;
const verificationResult = await sdk.verifyCredential(decryptedCredential);
console.log('Verification result:', verificationResult);
```
```json Response theme={null}
{
"isValid": true
}
```
Verifying the status of the NFT related to the credential requires connection to the internet in order to access
blockchain state.
For details on the SDK functionality, review our [SDK reference documentation](/sdk-reference/credentials/introduction).
## Independent Verification
Since Crossmint’s Verifiable Credentials are based on the [W3C standard](https://www.w3.org/TR/vc-data-model-2.0/), you can use any library that supports this standard to verify a credential.
The W3C Verifiable Credentials standard ensures interoperability across different platforms and systems, enhancing the reliability and security of the credential verification process.
Crossmint verification is stricter and performs additional checks that are not part of the W3C standard. For example, that no additional fields have been added to the credential.
Test any API in seconds directly from the docs.
Contact our sales team for support.
# Webhook
Source: https://docs.crossmint.com/minting/verifiable-credentials/integrate/webhooks
Receive updates about your credentials
Webhooks are a critical component in the management and distribution of verifiable credentials, providing real-time notifications and seamless integration for issuers. Here’s why webhooks enhance the credential issuance process:
* **Automatic Credential Retrieval**: Upon issuance, the credential is sent directly to the issuer through the webhook payload. This eliminates the need for issuers to manually call `getCredential()`. This functionality is especially crucial when the issuer opts for delegated storage; without catching the webhook, the credential could be lost, as there would be no subsequent retrieval.
* **Notification of Credential Issuance Failure**: Webhooks provide immediate alerts if the credential issuance process fails. This enables issuers to quickly identify and address issues, ensuring a reliable and efficient credential issuance process.
* **Secure Storage of Encryption Keys**: In scenarios involving encryption via the "crossmint-recoverable" encryption type, the webhook stores the encryption key for the recipient subject. This ensures that, in case of any fallback, the key is readily available, maintaining the security and accessibility of the encrypted credentials.
* **Immediate Access to Credential Pass Links**: Issuers can instantly receive a link to the wallet credential pass via the webhook. This link can then be forwarded to the subject, ensuring a swift and efficient delivery of credentials.
If you choose not to use a webhook, you can query for the credential's status via the [status API](https://docs.crossmint.com/api-reference/minting/nfts/mint-status).
## Setting Up a Webhook
First, you need to [setup a webhook](https://docs.crossmint.com/docs/mintapi-webhooks) in the developer console. Once that is done either programmatically or via the Dev Console, the webhook payload received, looks as follows:
```json theme={null}
data:{
credential: credentialObject or encryptedCredentialObjcet
pass: wallet pass object
secret: encryption key
}
```
The events you can monitor are:
* `credential.creation.succeeded`
Will be emitted when the credential creation process is complete.
The issued credential, credential pass, and encryption key will be contained in the `data.credential` field, `data.pass` field, and `data.secret` field, respectively.
In case of encrypted credential templates, the issued credential returned will be encrypted.
* `credential.creation.failed`
Will be emitted when the credential creation process fails. The error will be contained in the `data.error` field.
# How It Works
Source: https://docs.crossmint.com/offramp/concepts/how-it-works
How your users convert USDC into fiat and withdraw to their bank account
Crossmint Offramp lets your users convert USDC into fiat and withdraw it to their own bank account without leaving your app. You integrate once, and Crossmint handles compliance, conversion, and the bank payout. This page explains how the flow works end to end before you build it.
## The offramp flow
Each user completes identity verification once. Verification runs asynchronously, so you trigger it and then poll until the user is verified. If your users are already KYC'd, you can bring them over by passing their identity data, so they skip the in-flow verification. See [KYC & Compliance](/offramp/concepts/kyc-and-compliance).
The user's bank details are saved as a reusable payment method. Sensitive fields are tokenized at capture, so raw account numbers never touch your servers or Crossmint's.
Your backend creates an order specifying the amount of USDC to cash out and the bank account to pay. The order comes back with a prepared deposit transaction that already carries a memo tying the deposit to this order.
Your app broadcasts the prepared transaction from the user's wallet. Crossmint matches the on-chain deposit to the order by its memo, so no extra confirmation call is needed.
Crossmint converts the USDC and pays out fiat to the user's bank account over local rails.
The order completes and the funds settle in the user's bank. You can track status by polling the order or via webhooks.
## Key concepts
### Exact-in amounts
You specify the **amount of USDC the user sends** (`exact-in`). The user sends exactly that amount of crypto, and the equivalent fiat, minus any applicable fees, is deposited into their bank account.
### First-party payouts
The destination bank account must belong to the same user who owns the wallet and completed verification. The wallet the user pays from is linked to that verified identity, and the order resolves KYC through it. Offramp pays users out to **their own** bank accounts, never to third parties.
### Settlement timing
Once the USDC deposit is confirmed, fiat arrival depends on the payout rail, ranging from minutes to a few business days. Crossmint selects the fastest available rail for the destination. See [Payment Schemes](/offramp/payment-methods) for rail-specific settlement times.
In the staging environment, payouts are **simulated**, so you can run the full flow end to end without funds moving. Funds only move in production.
## What Crossmint handles vs. what you build
| Crossmint handles | You build |
| :-------------------------------- | :------------------------------------------- |
| Identity screening and compliance | The in-app cash-out experience |
| Converting USDC to fiat | Collecting the user's bank account |
| Paying out over local bank rails | Creating orders and tracking status |
| Regulatory and licensing coverage | (Optional) passing KYC data you already hold |
## Next steps
Build your first offramp end to end
How verification works for offramp
# KYC & Compliance
Source: https://docs.crossmint.com/offramp/concepts/kyc-and-compliance
How identity verification and compliance work for Crossmint Offramp
Every end user must pass KYC before they can withdraw funds through the Offramp. You can collect this verification with a Crossmint-hosted flow, or pass data you have already verified yourself. This page covers the onboarding models, the KYC tiers, and where to find the exact data requirements.
## Choose Your Onboarding Model
| Model | Best for | How it works |
| :------------------- | :-------------------------------------- | :------------------------------------------------------------------------- |
| **Import KYC data** | Platforms that already verify users | You pass user data to Crossmint via API (Sumsub token-sharing coming soon) |
| **Crossmint-hosted** | Teams that want the fastest integration | The user completes KYC inside secure embedded components |
## How Verification Works
Verification runs asynchronously. You register the user, trigger verification, then poll until their `offramp` eligibility reads `verified`. Only verified users can save a payment method or create an order. The [Identity quickstart](/identity/quickstart) walks through the full sequence (accept the privacy policy, register the user, trigger verification, upload any required documents, and poll status), and its response reports eligibility per product, including `offramp` (full KYC) and `offramp-light` (progressive KYC). These endpoints use a `userId:` locator, a unique identifier you choose for the user.
## KYC Tiers
Crossmint applies KYC tiers based on cumulative transaction volume, transaction frequency, and risk profile — the same model used for [Onramp](/onramp/introduction/user-onboarding):
| Tier | Applies When | Data Required |
| :------------------------------- | :----------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Progressive (light) KYC** | Up to \$1,000 in total volume in the last 12 months | Name, date of birth, nationality, country, address, email, and phone number (phone required for US residents). An ID number or SSN is required for US (SSN) and EU/EEA residents; optional elsewhere. No document uploads. |
| **Full KYC** | Over \$1,000 in total volume in the last 12 months, or a high number of transactions | Progressive KYC data plus `dueDiligence`, `verificationHistory`, identity document, and selfie. See the [data requirements](/identity/data-requirements) page. |
| **Enhanced Due Diligence (EDD)** | High-risk individuals or regions | Full KYC data plus proof of address and proof of income |
Light KYC is available to non-US residents; US residents always complete Full KYC. Identity verification eligibility reports both tiers per user: `offramp-light` (progressive) and `offramp` (full).
## Guides
# Collect Bank Accounts
Source: https://docs.crossmint.com/offramp/guides/collect-bank-accounts
Save and manage your users' bank accounts as reusable payment methods
Before a user can cash out, you [save their bank account](/api-reference/headless/payment-methods/create-payment-method) as a **payment method**. Saved payment methods are reusable across Crossmint products, so a user enters their details once.
## Save a bank account
The request body depends on the payment method `type`. See [The Payment Method Object](/api-reference/headless/payment-methods/payment-method-object) for the full list of types and fields.
The examples below use the staging URL, `https://vault.staging.crossmint.com`. In production, use `https://vault.crossmint.com`. See [Launch in Production](/offramp/guides/going-to-production) for all the differences between environments.
```bash theme={null}
curl -X POST 'https://vault.staging.crossmint.com/api/unstable/payment-methods' \
-H 'X-API-KEY: ' \
-H 'Content-Type: application/json' \
-d '{
"type": "bank-account-us",
"userLocator": "userId:",
"bankAccount": {
"accountNumber": "6349881141",
"routingNumber": "026009593",
"accountType": "checking",
"bankName": "Chase",
"bankAddress": {
"line1": "420 Montgomery St",
"city": "San Francisco",
"stateOrRegion": "CA",
"postalCode": "94104",
"country": "US"
},
"currency": "usd",
"country": "US",
"entityType": "individual",
"billing": {
"name": "Alice Smith",
"phone": "+13055551234",
"address": {
"line1": "701 S Miami Ave",
"city": "Miami",
"stateOrRegion": "FL",
"postalCode": "33156",
"country": "US"
}
}
}
}'
```
```bash theme={null}
curl -X POST 'https://vault.staging.crossmint.com/api/unstable/payment-methods' \
-H 'X-API-KEY: ' \
-H 'Content-Type: application/json' \
-d '{
"type": "bank-account-sepa-iban",
"userLocator": "userId:",
"bankAccount": {
"iban": "DE89370400440532013000",
"bic": "COBADEFFXXX",
"currency": "eur",
"country": "DE",
"entityType": "individual",
"billing": {
"name": "Hans Müller",
"phone": "+4915112345678",
"address": {
"line1": "Unter den Linden 1",
"city": "Berlin",
"stateOrRegion": "BE",
"postalCode": "10117",
"country": "DE"
}
}
}
}'
```
```bash theme={null}
curl -X POST 'https://vault.staging.crossmint.com/api/unstable/payment-methods' \
-H 'X-API-KEY: ' \
-H 'Content-Type: application/json' \
-d '{
"type": "bank-account-mx-clabe",
"userLocator": "userId:",
"bankAccount": {
"accountNumber": "138180000123459719",
"currency": "mxn",
"country": "MX",
"entityType": "individual",
"billing": {
"name": "Javier Toledo",
"phone": "+525512345678",
"address": {
"line1": "Av. Reforma 1",
"city": "Mexico City",
"stateOrRegion": "CDMX",
"postalCode": "06600",
"country": "MX"
}
}
}
}'
```
```bash theme={null}
curl -X POST 'https://vault.staging.crossmint.com/api/unstable/payment-methods' \
-H 'X-API-KEY: ' \
-H 'Content-Type: application/json' \
-d '{
"type": "bank-account-co",
"userLocator": "userId:",
"bankAccount": {
"accountNumber": "123456787890",
"bankCode": "001",
"accountType": "savings",
"documentType": "CC",
"documentNumber": "1234567890",
"currency": "cop",
"country": "CO",
"entityType": "individual",
"billing": {
"name": "Carlos Gómez",
"phone": "+573001234567",
"address": {
"line1": "Carrera 7",
"city": "Bogotá",
"stateOrRegion": "DC",
"postalCode": "110010",
"country": "CO"
}
}
}
}'
```
The response returns the saved payment method, including its `paymentMethodId`, the resolved `bankName`, and a masked `accountSuffix` (banks use `accountSuffix`, not `last4`). Pass the `paymentMethodId` as the `recipient` when you [create an order](/offramp/quickstarts/rest-api).
```json theme={null}
{
"paymentMethodId": "d5e5c48a-7dec-44b1-8cf4-4615bf00c8fc",
"default": false,
"displayName": "Chase ••1141",
"createdAt": "2026-06-17T21:25:56.542Z",
"updatedAt": "2026-06-17T21:25:56.542Z",
"type": "bank-account-us",
"bankAccount": {
"billing": {
"name": "Alice Smith",
"phone": "+13055551234",
"address": {
"line1": "701 S Miami Ave",
"city": "Miami",
"stateOrRegion": "FL",
"postalCode": "33156",
"country": "US"
}
},
"bankName": "Chase",
"accountSuffix": "1141",
"currency": "usd",
"country": "US",
"entityType": "individual",
"bankAddress": {
"line1": "420 Montgomery St",
"city": "San Francisco",
"stateOrRegion": "CA",
"postalCode": "94104",
"country": "US"
},
"routingNumber": "026009593",
"accountType": "checking"
}
}
```
Saving an account does not require the user to be verified yet, but creating an order does. Before you offramp with this account, verify the user and link the wallet they will pay from. See the [Quickstart](/offramp/quickstarts/rest-api).
For a complete list of fields and supported bank account types, see [The Payment Method Object](/api-reference/headless/payment-methods/payment-method-object) in the API reference.
## List, update, and remove
Reads and non-sensitive updates go through the standard API. Use it to [list a user's payment methods](/api-reference/headless/payment-methods/list-payment-methods), [fetch one](/api-reference/headless/payment-methods/get-payment-method), [update billing details](/api-reference/headless/payment-methods/update-payment-method), or [remove an account](/api-reference/headless/payment-methods/delete-payment-method).
## Next steps
Use a saved account in an offramp
Verification requirements
# Launch in Production
Source: https://docs.crossmint.com/offramp/guides/going-to-production
Move your offramp integration from staging to live payouts
Everything you build on staging runs end to end with simulated payouts. When you are ready to move funds to users' banks, switch to production.
## What changes
| | Staging | Production |
| :----------------------- | :------------------------------------------------------- | :------------------------------------------------- |
| API host | `staging.crossmint.com` | `www.crossmint.com` |
| Vault host | `vault.staging.crossmint.com` | `vault.crossmint.com` |
| API key | Staging server key | Production server key |
| Crypto | Testnet USDC | Mainnet USDC |
| Chain (`payment.method`) | Testnet (for example `base-sepolia`, `ethereum-sepolia`) | Mainnet (for example `base`, `solana`, `ethereum`) |
| Payout | Simulated | Fiat settles in the user's bank account |
Get your production API key from the Crossmint console. It needs the `orders`, `payment-methods`, `users.create`, and `users.read` scopes.
## Go-live checklist
Production access requires a signed Order Form and completed KYB verification, and your project enabled for KYC data sharing. Contact sales to get set up, and see [account verification](/introduction/platform/account-verification) for what KYB involves.
Verify each user in production. If you import KYC data, confirm it meets the [data requirements](/identity/data-requirements) for each region you serve.
Wire up [order tracking](/offramp/guides/manage-orders) by polling or webhooks so your app reflects payout progress and surfaces failures.
Point your requests at the production hosts and swap in your production API key. Send mainnet USDC on a supported chain.
## Next steps
Enable production access
Verification requirements
# Import User KYC Data
Source: https://docs.crossmint.com/offramp/guides/import-user-kyc-data
Pass your users' existing KYC data to Crossmint so they skip verification
If your platform already verifies your users, share that data with Crossmint so they skip in-flow verification and are ready to cash out. This guide covers passing that data and confirming the user is verified. For how verification works in depth, see [KYC & Compliance](/offramp/concepts/kyc-and-compliance).
## Register and verify the user
Each call uses a `userId:` locator, where `` is a unique identifier you choose for the user and reuse across the calls below.
Before sharing any user KYC data with Crossmint, the user must accept Crossmint's Privacy Policy in your app. Consent must be collected with an explicit checkbox and specific wording — see [Register User Data](/identity/register-user-data) for the exact consent-collection requirements. Only once the user has accepted, record their consent:
```bash theme={null}
curl -X PUT 'https://staging.crossmint.com/api/2025-06-09/users/userId:/legal-documents' \
-H 'X-API-KEY: ' \
-H 'Content-Type: application/json' \
-d '{ "type": "crossmint-privacy-policy", "acceptedAt": "2026-01-01T00:00:00.000Z" }'
```
```json theme={null}
{
"acceptedAt": "2026-01-01T00:00:00.000Z"
}
```
Send the user's details along with the KYC data you already hold.
```bash theme={null}
curl -X PUT 'https://staging.crossmint.com/api/2025-06-09/users/userId:' \
-H 'X-API-KEY: ' \
-H 'Content-Type: application/json' \
-d '{
"userDetails": {
"firstName": "Alice",
"lastName": "Smith",
"dateOfBirth": "1990-01-15",
"countryOfResidence": "US"
},
"kycData": {
"email": "alice@example.com",
"phoneNumber": "+13055551234",
"nationality": "US",
"addressOfResidence": {
"line1": "701 S Miami Ave",
"city": "Miami",
"postalCode": "33156",
"stateOrRegion": "FL"
},
"identityDocument": {
"type": "ssn",
"number": "123-45-6789",
"issuingCountryCode": "US"
},
"ipAddresses": ["203.0.113.1"]
},
"verificationHistory": {
"idVerificationTimestamp": "2026-01-01T00:00:00.000Z",
"livenessVerificationTimestamp": "2026-01-01T00:00:00.000Z"
}
}'
```
The booleans in the response show which sections were stored.
**US residents:** include `kycData.phoneNumber` (required), and provide identity via SSN in `kycData.identityDocument` (no document upload is needed for standard offramp). The `dueDiligence` object is not required for standard US offramp in most states — include it only where required (see [Data Requirements](/identity/data-requirements)) or for Enhanced Due Diligence, which adds document checks.
```json theme={null}
{
"userId": "",
"userDetails": true,
"kycData": true,
"dueDiligence": false,
"verificationHistory": true
}
```
Trigger verification so Crossmint runs its compliance checks against the data you provided.
```bash theme={null}
curl -X PUT 'https://staging.crossmint.com/api/2025-06-09/users/userId:/identity-verification' \
-H 'X-API-KEY: '
```
The response returns the eligibility status per product, starting at `pending-review`.
```json theme={null}
{
"eligibility": [
{ "type": "onramp", "status": "pending-review" },
{ "type": "onramp-light", "status": "pending-review" },
{ "type": "offramp", "status": "pending-review" },
{ "type": "offramp-light", "status": "pending-review" },
{ "type": "regulated-transfer", "status": "pending-review" }
]
}
```
Verification is asynchronous. Poll until the `offramp` eligibility reads `verified`. Only a verified user can save a payment method or create an order.
```bash theme={null}
curl -X GET 'https://staging.crossmint.com/api/2025-06-09/users/userId:/identity-verification' \
-H 'X-API-KEY: '
```
```json theme={null}
{
"eligibility": [
{ "type": "onramp", "status": "verified" },
{ "type": "onramp-light", "status": "verified" },
{ "type": "offramp", "status": "verified" },
{ "type": "offramp-light", "status": "verified" },
{ "type": "regulated-transfer", "status": "verified" }
]
}
```
The exact fields required vary by region. See the [Identity data requirements](/identity/data-requirements) for the full list, and the [Identity quickstart](/identity/quickstart) for uploading documents when a region requires them.
The account holder name on the user's saved bank account must match this verified identity. Offramp supports first-party payouts only.
## Next steps
Save the verified user's bank account
Build the full flow
# Manage Orders
Source: https://docs.crossmint.com/offramp/guides/manage-orders
Track offramp order status through every phase, handle webhooks, and troubleshoot common issues
After you create an offramp order, you track it from the stablecoin deposit through to the fiat payout arriving in your user's bank account. This guide shows you how to monitor that journey, react to each status correctly, and handle every outcome along the way.
In this guide, you will learn how to:
* Check an order's status and know which fields to watch
* Follow an order through each phase of its lifecycle
* Map each status to the right UI state and next action
* Receive order updates through webhooks instead of polling
* Troubleshoot expired quotes and rejected payouts
## Check order status
Fetch an order any time with its `orderId` (see the [Get Order API reference](/api-reference/headless/get-order) for the complete response schema):
```bash theme={null}
curl -X GET \
'https://staging.crossmint.com/api/2022-06-09/orders/' \
-H 'X-API-KEY: '
```
The response includes the full order object (see the [Quickstart](/offramp/quickstarts/rest-api) for the full end-to-end flow). The key fields to watch are:
* **`phase`**: the high-level stage of the order
* **`payment.status`**: whether the stablecoin deposit has been received
* **`lineItems[0].delivery.status`**: whether the fiat payout is pending, in transit, completed, or failed
If you poll instead of using webhooks, use the [status-handling reference](#status-handling-reference) below to decide when to stop and what to show next.
Below is a full completed-order response with the key fields highlighted:
```json {3,22,44} theme={null}
{
"orderId": "d3d1e4d8-1aae-45f1-804a-4ca674215bae",
"phase": "completed",
"locale": "en-US",
"lineItems": [
{
"metadata": {
"name": "USD",
"description": "United States Dollar",
"imageUrl": "https://www.crossmint.com/assets/ui/flags/us.svg"
},
"quote": {
"status": "expired",
"charges": {
"unit": { "amount": "1", "currency": "usdc" },
"fees": { "type": "exact", "amount": "0.002", "currency": "usdc" }
},
"totalPrice": { "amount": "1", "currency": "usdc" },
"quantityRange": { "lowerBound": "1", "upperBound": "1" }
},
"delivery": {
"status": "completed",
"recipient": {
"locator": "paymentMethodId:fb04b8bb-2bba-47ba-a105-66a93f61d468",
"paymentMethodId": "fb04b8bb-2bba-47ba-a105-66a93f61d468"
}
},
"executionMode": "exact-in",
"callData": {
"mode": "exact-in",
"amount": "1",
"outFiatCurrency": "usd",
"effectiveAmount": "0.998"
},
"quantity": 1
}
],
"quote": {
"status": "expired",
"quotedAt": "2026-06-18T15:47:51.931Z",
"expiresAt": "2026-06-18T15:57:51.931Z"
},
"payment": {
"status": "completed",
"method": "base",
"currency": "usdc",
"preparation": {
"chain": "base",
"payerAddress": "0x64EC529c6525CeD1Bd565a954a725787E31a89b9"
},
"received": {
"chain": "base",
"txId": "0xc40255de1c9028d1c6401249f5f4a2815749b284f2325d94d231808c2633b3d8",
"amount": "1",
"currency": "usdc"
},
"receiptEmail": "user@example.com"
},
"legal": {
"requirements": [
{
"display": "show-checkbox",
"type": "crossmint-terms-of-service",
"url": "https://www.crossmint.com/legal/crossmint-terms-of-service/TRGUSAALLALLALL"
},
{
"display": "show-text",
"type": "crossmint-privacy-policy",
"url": "https://www.crossmint.com/legal/crossmint-privacy-policy/ALLALLALLALLALL"
}
]
}
}
```
A completed order can still show `quote.status: "expired"` because the quote time window closes independently of payment and delivery. By itself, this does not indicate a problem. Rely on `phase`, `payment.status`, and `lineItems[].delivery.status` to determine the order outcome.
## Order lifecycle
The full status model has four phases. Offramp orders start in `payment`, with the quote included inside the order, then move through `delivery` to `completed` when the payout reaches a terminal outcome. The `quote` phase appears if the quote expires before the deposit arrives.
After the order is created and the quote is locked, the order sits in the `payment` phase until the stablecoin deposit arrives on-chain and is matched by memo.
```json theme={null}
{
"phase": "payment",
"payment": { "status": "awaiting-payment" },
"lineItems": [{ "delivery": { "status": "awaiting-payment" } }]
}
```
If no deposit arrives before the quote expires, the order returns to the `quote` phase with `payment.status: "requires-quote"`. See [Quote expired](#troubleshooting) below.
Once the deposit lands, Crossmint converts the stablecoin and initiates the fiat payout. The order moves to the `delivery` phase.
```json theme={null}
{
"phase": "delivery",
"payment": {
"status": "completed",
"received": {
"chain": "base-sepolia",
"txId": "0xc953...a338e85",
"amount": "1",
"currency": "usdc"
}
},
"lineItems": [{ "delivery": { "status": "in-progress" } }]
}
```
Offramp settlement has two asynchronous parts: receiving the stablecoin deposit and completing the fiat payout. After `payment.status` becomes `completed`, the order may remain in `delivery` while the payout is being prepared, processed by the rail, or delayed by weekends and bank holidays. For rail-specific timing, see [Payment Schemes](/offramp/payment-methods).
Once the bank transfer completes, the order moves to `completed` with a successful delivery status.
```json theme={null}
{
"phase": "completed",
"payment": {
"status": "completed",
"received": {
"chain": "base-sepolia",
"txId": "0xc953...a338e85",
"amount": "1",
"currency": "usdc"
}
},
"lineItems": [{ "delivery": { "status": "completed" } }]
}
```
`phase: "completed"` means the order is **terminal**, not necessarily successful. Always check `lineItems[0].delivery.status` to determine the outcome: `"completed"` (success) or `"failed"` (see [Bank rejection](#troubleshooting)).
## Order status reference
Each field below returns one of a fixed set of values. Expand a field for the full list.
| Value | Meaning |
| :---------- | :---------------------------------------------------------------------- |
| `quote` | Quote pending or expired; no deposit has been taken yet |
| `payment` | Quote locked; waiting for or processing the on-chain stablecoin deposit |
| `delivery` | Stablecoin deposit received; fiat payout is pending or in progress |
| `completed` | Terminal. Check `delivery.status` for the outcome |
In the standard offramp flow, `payment.status` typically moves through `awaiting-payment`, `in-progress`, and `completed`. The other statuses listed below represent missing setup steps, verification requirements, or payer-wallet conditions.
| Value | Meaning |
| :---------------------------------------- | :---------------------------------------------------------------------------------------------------------------------------------- |
| `requires-quote` | A new or refreshed quote is needed (for example, the previous quote expired) |
| `requires-crypto-payer-address` | The payer wallet address has not been set yet |
| `requires-email` | A receipt email is required before payment can proceed |
| `requires-kyc` | Identity verification (KYC) is required before payment can proceed |
| `pending-kyc-review` | KYC submission is under review |
| `manual-kyc` | KYC requires manual review |
| `failed-kyc` | KYC verification was rejected |
| `requires-recipient-verification` | The recipient must complete wallet ownership verification |
| `awaiting-deposit-instructions` | Deposit instructions are being prepared; wait until the order returns `awaiting-payment` before asking the user to send the deposit |
| `awaiting-payment` | Quote accepted; waiting for the user to send the stablecoin deposit |
| `crypto-payer-insufficient-funds` | The payer wallet does not have enough funds for the deposit |
| `crypto-payer-insufficient-funds-for-gas` | The payer wallet does not have enough native token to pay network gas |
| `in-progress` | Stablecoin deposit detected; waiting for on-chain confirmations |
| `completed` | Stablecoin deposit received and matched to the order |
| Value | Meaning |
| :----------------- | :---------------------------------------------------------------------- |
| `awaiting-payment` | Fiat payout not yet initiated |
| `in-progress` | Fiat payout initiated; awaiting rail completion |
| `completed` | Fiat payout completed |
| `failed` | Payout could not be completed (for example, bank rejected the transfer) |
When a deposit cannot be processed or a payout fails, the order response may include a structured failure reason:
* **`payment.failureReason`**: may be present when the deposit cannot be processed. Shape: `{ code, message?, developerMessage? }`.
* **`lineItems[].delivery.failureReason`**: may be present when the fiat payout fails. Shape: `{ code, message? }`. Possible codes: `transfer-failed`, `slippage-tolerance-exceeded`, `payment-refunded`.
Payment failure reasons are returned on the order object. If you need to detect deposit-processing issues before a delivery event fires, refetch the order with the [Get Order API](/api-reference/headless/get-order) and inspect `payment.status` and `payment.failureReason`.
## Status-handling reference
Use this table to decide what to show the user and what action to take:
| `phase` | `payment.status` | `delivery.status` | Meaning | Developer action |
| :---------- | :----------------- | :----------------- | :---------------------------------------------------------------- | :-------------------------------------------------------------------------------------------- |
| `payment` | `awaiting-payment` | `awaiting-payment` | Waiting for stablecoin deposit | Show deposit instructions |
| `payment` | `in-progress` | `awaiting-payment` | Stablecoin deposit detected; waiting for on-chain confirmations | Show "Payment confirming"; do not ask the user to resend funds |
| `delivery` | `completed` | `awaiting-payment` | Stablecoin deposit received; fiat payout not yet initiated | Show "Payout pending"; do not ask the user to resend funds |
| `delivery` | `completed` | `in-progress` | Stablecoin deposit received; fiat payout awaiting rail completion | Show "Payout on the way"; do not ask the user to resend funds |
| `completed` | `completed` | `completed` | Payout completed successfully | Show "Funds sent" confirmation |
| `completed` | `completed` | `failed` | Payout failed | Check `payment.refunded` for refund details; prompt user to retry with corrected bank details |
| `quote` | `requires-quote` | `awaiting-payment` | Quote expired before deposit | Create a new order with a fresh quote |
These statuses may appear during order setup or when the payer wallet has issues. Use `payment.status` as the key to decide what to show:
| `payment.status` | Meaning | Developer action |
| :---------------------------------------- | :----------------------------------------------------------- | :---------------------------------------------------------------------------------- |
| `requires-crypto-payer-address` | Payer wallet address is missing | Provide the payer wallet address, then refetch the order |
| `requires-email` | Receipt email is required | Collect the email, then refetch the order |
| `requires-kyc` | Identity verification is required | Send the user through verification before showing deposit instructions |
| `pending-kyc-review`, `manual-kyc` | Identity verification is under review | Show verification pending and wait for the status to update |
| `failed-kyc` | Identity verification was rejected | Do not ask the user to send funds; show the verification failure state |
| `requires-recipient-verification` | Wallet ownership verification is required | Complete recipient verification, then refetch the order |
| `awaiting-deposit-instructions` | Deposit instructions are being prepared | Wait until the order returns `awaiting-payment` before showing deposit instructions |
| `crypto-payer-insufficient-funds` | Payer wallet does not have enough stablecoin for the deposit | Ask the user to fund the wallet or use another wallet |
| `crypto-payer-insufficient-funds-for-gas` | Payer wallet does not have enough native token for gas | Ask the user to add gas or use a wallet with gas sponsorship |
## Webhooks
Instead of polling, subscribe to order events so Crossmint notifies your backend as the order progresses. Configure an endpoint in the Crossmint console and subscribe to events using the [Webhooks guide](/payments/advanced/webhooks).
The events relevant to the standard offramp progression are:
| Event | Fires when |
| :-------------------------- | :------------------------------------------------------ |
| `orders.payment.succeeded` | The on-chain stablecoin deposit is matched to the order |
| `orders.delivery.initiated` | The fiat payout has been initiated |
| `orders.delivery.completed` | The fiat payout has completed |
| `orders.delivery.failed` | The payout could not be completed |
Use these events to drive your UI updates. For example, show the user "payout on the way" when you receive `orders.delivery.initiated` and "funds sent" when you receive `orders.delivery.completed`.
## Troubleshooting
An offramp order spans two independent systems: an on-chain stablecoin deposit and a fiat payout over banking rails. Most orders complete on their own, but a few situations can interrupt the standard flow, most often a quote that expires before the deposit arrives or a payout the bank declines. Both are expected and recoverable. Expand a case below to see how to detect and resolve it.
If the user does not send the stablecoin deposit before the quote expires, the order returns to the `quote` phase. No funds are collected.
```json theme={null}
{
"phase": "quote",
"quote": { "status": "expired" },
"payment": { "status": "requires-quote" },
"lineItems": [{ "delivery": { "status": "awaiting-payment" } }]
}
```
The exchange rate is stale. Create a new order to get a fresh quote.
If the bank rejects the payout after the stablecoin deposit has been received (for example, invalid account details), the order moves to `completed` with a failed delivery. The buyer deposit is automatically refunded.
```json theme={null}
{
"phase": "completed",
"payment": {
"status": "completed",
"refunded": { "amount": "1", "currency": "usdc" }
},
"lineItems": [
{
"delivery": {
"status": "failed",
"failureReason": { "code": "transfer-failed" }
}
}
]
}
```
Check `payment.refunded` to confirm the refund details. Listen for `orders.delivery.failed` so you can surface the issue in your app and prompt the user to try again with corrected bank details.
## Next steps
Build the full flow
Move from staging to live payouts
# Overview
Source: https://docs.crossmint.com/offramp/overview
Allow your users to convert stablecoins to fiat currency
Crossmint Offramp lets your users convert stablecoins into fiat and withdraw funds directly to their bank account through a fully white-labeled, native experience inside your app.
The product is delivered through a single API that handles compliance, liquidity, and bank payouts, so you can add cash-out without managing banking relationships or regulatory infrastructure. Embedded widget and SDK support is coming soon.
**Offramping from a corporate treasury wallet?** See the [Treasury offramps guide](/stablecoin-orchestration/guides/on-off-ramps/withdrawing-from-company-wallets).
## Key Features
Seamless in-app experiences
Same API for global access
Import user data (optional)
## Next Steps
## Guides
KYC Data Imports
Collect Bank Accounts
Track order status
# Payment Schemes
Source: https://docs.crossmint.com/offramp/payment-methods
Supported payout rails and geographies for Crossmint Offramp
Crossmint Offramp supports multiple payout rails across geographies. The table below lists the available schemes by currency.
| Rail/Scheme | Availability | Settlement | Cut-off Time (working days) | Transaction Limits |
| :-------------------------------------- | :------------------------------------------------------------------ | :------------------- | :-------------------------- | :----------------------------- |
| **ACH** (USD) | Available | 1 business day | GMT 01:45pm to 08:00pm | \$50,000 per transaction |
| **Same-day ACH** (USD) | Available | Same day | GMT 01:45pm to 08:00pm | \$50,000 per transaction |
| **RTP** (USD) | Available | Instant | 24/7 | \$1,000,000 per transaction |
| **Wire** (USD) | Available | Same day | GMT 01:45pm to 08:00pm | No limit |
| **SEPA Instant** (EUR) | July | Seconds | 24/7 | €500,000 per transaction |
| **SEPA** (EUR) | July | 1 to 2 business days | GMT 07:00am to 02:00pm | No limit |
| **SWIFT** (EUR) | July | 1 to 5 business days | GMT 07:00am to 02:00pm | No limit |
| **T2** (EUR) | Q3 | Same day | | |
| **Faster Payments** (GBP) | B2B: July/August, Consumer: Q3/Q4 | Minutes to hours | 24/7 | £1,000,000 per transaction |
| **SPEI** (MXN) | July/August | Minutes | | |
| **Bre-B** (COP) | July/August | Instant | | COP 12,110,000 per transaction |
| **Bank Transfer** (COP) | July/August | 1 to 2 business days | | |
| **Pix** (BRL) | July/August | Instant | | |
| Additional rails available upon request | Contact sales | | | |
Settlements arrive within the stated timeframes only if payments are submitted by the stipulated cut-off time (COT).
### US (ACH, Same-day ACH, Wire)
| Timezone | Winter (Standard) | Summer (Daylight) |
| :------- | :--------------------- | :--------------------- |
| PT | 05:45am to 12:00pm PST | 06:45am to 01:00pm PDT |
| ET | 08:45am to 03:00pm EST | 09:45am to 04:00pm EDT |
### Europe and UK (SEPA, SWIFT)
| Timezone | Winter (Standard) | Summer (Daylight) |
| :------- | :--------------------- | :--------------------- |
| PT | 11:00pm to 06:00am PST | 12:00am to 07:00am PDT |
| ET | 02:00am to 09:00am EST | 03:00am to 10:00am EDT |
# REST API Quickstart
Source: https://docs.crossmint.com/offramp/quickstarts/rest-api
Allow users to offramp stablecoins to fiat
In this guide, you will run a full USDC to USD offramp in the Staging environment. You will:
* Confirm Offramp eligibility
* Save the user's payout method
* Set up the wallet the user pays from
* Create an Offramp order and read its deposit instructions
* Broadcast the deposit and track the order to completion
## Prerequisites
* A **server-side API key** with the `orders` and `payment-methods` scopes (Crossmint console).
* A user **KYC-verified for Offramp**. To verify a user, follow [Import User KYC Data](/offramp/guides/import-user-kyc-data) or the [Identity quickstart](/identity/quickstart). This quickstart uses `userId:` in examples, but the API accepts any supported `userLocator`.
* **USDC** to cash out. On staging, get testnet USDC from the Circle faucet.
* **Native gas tokens** in the sender wallet to broadcast the deposit transaction (e.g., ETH on Base, SOL on Solana). This does not apply if the wallet sponsors gas.
Offramp uses Crossmint's user KYC API. Contact sales to enable it for your project.
Before creating an order, confirm that the user is verified for Offramp. Verification is asynchronous. Poll the user's status with the Get Identity Verification endpoint until the `offramp` eligibility reads `verified`:
```bash theme={null}
curl -X GET 'https://staging.crossmint.com/api/2025-06-09/users//identity-verification' \
-H 'X-API-KEY: '
```
`` is the Crossmint user locator for the verified user, for example `userId:` or `email:`. See the Get Identity Verification API reference for all supported locator formats.
```json theme={null}
{
"eligibility": [
{ "type": "onramp", "status": "verified" },
{ "type": "onramp-light", "status": "verified" },
{ "type": "offramp", "status": "verified" },
{ "type": "offramp-light", "status": "verified" },
{ "type": "regulated-transfer", "status": "verified" }
]
}
```
Save the user's payout destination as a reusable payment method. The example below saves a US bank account. Sensitive fields are saved securely.
See the Create Payment Method API reference for the full request schema and supported payment method types.
```bash theme={null}
curl -X POST 'https://vault.staging.crossmint.com/api/unstable/payment-methods' \
-H 'X-API-KEY: ' \
-H 'Content-Type: application/json' \
-d '{
"type": "bank-account-us",
"userLocator": "",
"bankAccount": {
"accountNumber": "6349881141",
"routingNumber": "026009593",
"accountType": "checking",
"bankName": "Chase",
"bankAddress": {
"line1": "420 Montgomery St",
"city": "San Francisco",
"stateOrRegion": "CA",
"postalCode": "94104",
"country": "US"
},
"currency": "usd",
"country": "US",
"entityType": "individual",
"billing": {
"name": "Alice Smith",
"phone": "+13055551234",
"address": {
"line1": "701 S Miami Ave",
"city": "Miami",
"stateOrRegion": "FL",
"postalCode": "33156",
"country": "US"
}
}
}
}'
```
The response returns the saved payment method. Banks expose a masked `accountSuffix` (not `last4`). Save the `paymentMethodId` for the order step.
```json theme={null}
{
"paymentMethodId": "d5e5c48a-7dec-44b1-8cf4-4615bf00c8fc",
"default": false,
"displayName": "Chase ••1141",
"createdAt": "2026-06-17T21:25:56.542Z",
"updatedAt": "2026-06-17T21:25:56.542Z",
"type": "bank-account-us",
"bankAccount": {
"billing": {
"name": "Alice Smith",
"phone": "+13055551234",
"address": {
"line1": "701 S Miami Ave",
"city": "Miami",
"stateOrRegion": "FL",
"postalCode": "33156",
"country": "US"
}
},
"bankName": "Chase",
"accountSuffix": "1141",
"currency": "usd",
"country": "US",
"entityType": "individual",
"bankAddress": {
"line1": "420 Montgomery St",
"city": "San Francisco",
"stateOrRegion": "CA",
"postalCode": "94104",
"country": "US"
},
"routingNumber": "026009593",
"accountType": "checking"
}
}
```
For listing, updating, and removing saved accounts, see [Collect Bank Accounts](/offramp/guides/collect-bank-accounts).
Choose the wallet the user will send USDC from. The payer wallet must belong to the same verified user who owns the payout method.
Create a wallet owned by the verified user, with a `server` signer so your backend can sign. Ownership links the wallet to the user, so there is no separate linking step, and deposits are gasless.
```typescript theme={null}
import { createCrossmint, CrossmintWallets } from "@crossmint/wallets-sdk";
const wallets = CrossmintWallets.from(
createCrossmint({ apiKey: process.env.CROSSMINT_SERVER_API_KEY })
);
const wallet = await wallets.createWallet({
chain: "base-sepolia",
owner: "userId:", // the user you verified
recovery: { type: "server", secret: process.env.CROSSMINT_SIGNER_SECRET }, // server admin signer
alias: "offramp-", // idempotent per (owner, alias)
});
```
* **`owner`** ties the wallet to the verified user via their locator (e.g., `userId:`). This ownership means no separate linking step is needed.
* **`recovery`** sets a `server` signer from your `CROSSMINT_SIGNER_SECRET`, a secret you generate and keep on your server (the SDK derives the signing key from it locally). See [Server Signer](/wallets/guides/signers/server-signer) to create one.
* **`alias`** is an optional label that makes the call idempotent, so re-running it with the same owner and alias returns the same wallet.
For all wallet options, see [Create a Wallet](/wallets/guides/create-wallet). Then fund `wallet.address` with USDC and use it as the payer. No native gas is needed.
Link the user's own wallet to the verified user. Make sure it holds USDC plus a little native gas for the deposit.
```bash theme={null}
curl -X PUT 'https://staging.crossmint.com/api/2025-06-09/users//linked-wallets/' \
-H 'X-API-KEY: ' \
-H 'Content-Type: application/json' \
-d '{ "chain": "base-sepolia" }'
```
```json theme={null}
{
"address": "0xAa266270cf90FEEA9760617d9c7B5083e9f08984",
"chain": "base-sepolia",
"type": "external-wallet",
"ownership": {
"verified": false,
"verificationChallenge": "crossmint.com wants you to sign in with your blockchain account..."
}
}
```
Each wallet links to one user, so use a separate wallet per user. The response returns an ownership challenge; on staging you do not need to sign it to proceed.
Create the order with the amount of USDC to cash out (`exact-in`), the chain you will pay from, and the saved bank account as the recipient. Use the payer wallet from the previous step as `payerAddress`.
```bash theme={null}
curl -X POST 'https://staging.crossmint.com/api/2022-06-09/orders' \
-H 'X-API-KEY: ' \
-H 'Content-Type: application/json' \
-d '{
"recipient": {
"paymentMethodId": ""
},
"payment": {
"method": "base-sepolia",
"currency": "usdc",
"payerAddress": "",
"receiptEmail": "alice@example.com"
},
"lineItems": {
"currencyLocator": "fiat:usd",
"executionParameters": {
"mode": "exact-in",
"amount": "1"
}
}
}'
```
The order comes back in the `payment` phase. Its **deposit instructions** live under `order.payment.preparation`: a ready to broadcast `serializedTransaction` (a USDC transfer that already carries the matching memo in its calldata) and the same memo in `transactionParameters.memo`. Keep the `order.orderId`.
```json theme={null}
{
"clientSecret": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJvcmRlcklkZW50aWZpZXIiOiI0MGY0OTgxMi0xMjNiLTRkMTAtYmRhZS01NjFlNGVkMmI5OTAiLCJjb2xsZWN0aW9uSWQiOiJjMjI0MGQ2Yy00NGM0LTQ1OTMtOGM4Yy1jZDRkYzJiZjE4MzUiLCJpYXQiOjE3ODE3MzI5MzQsImV4cCI6MTc4MTgxOTMzNH0.Na-AHOlge6-pYhpMszvITqe3ry49fk-o66i3gfz9a3I",
"order": {
"orderId": "40f49812-123b-4d10-bdae-561e4ed2b990",
"phase": "payment",
"locale": "en-US",
"lineItems": [
{
"metadata": {
"name": "USD",
"description": "United States Dollar",
"imageUrl": "https://www.crossmint.com/assets/ui/flags/us.svg"
},
"quote": {
"status": "valid",
"charges": {
"unit": { "amount": "1", "currency": "usdc" },
"fees": { "type": "exact", "amount": "0.002", "currency": "usdc" }
},
"totalPrice": { "amount": "1", "currency": "usdc" },
"quantityRange": { "lowerBound": "1", "upperBound": "1" }
},
"delivery": {
"status": "awaiting-payment",
"recipient": {
"locator": "paymentMethodId:d5e5c48a-7dec-44b1-8cf4-4615bf00c8fc",
"paymentMethodId": "d5e5c48a-7dec-44b1-8cf4-4615bf00c8fc"
}
},
"executionMode": "exact-in",
"callData": {
"mode": "exact-in",
"amount": "1",
"outFiatCurrency": "usd",
"effectiveAmount": "0.998"
},
"quantity": 1
}
],
"quote": {
"status": "valid",
"quotedAt": "2026-06-17T21:48:49.059Z",
"expiresAt": "2026-06-17T21:58:49.059Z",
"totalPrice": { "amount": "1", "currency": "usdc" }
},
"payment": {
"status": "awaiting-payment",
"method": "base-sepolia",
"currency": "usdc",
"preparation": {
"chain": "base-sepolia",
"payerAddress": "0xAa266270cf90FEEA9760617d9c7B5083e9f08984",
"serializedTransaction": "0x02f9018e83014a348083171dd9837ec1d582c3be94036cbd53842c5426634e7929541ec2318f3dcf7e80b90164a9059cbb000000000000000000000000bd1af733aed9d9f472a15a4ce13f42097bbc9eaa00000000000000000000000000000000000000000000000000000000000f42400a0a2d2d2d2d2d2d424547494e204d454d4f2d2d2d2d2d2d65794a68624763694f694a49557a49314e694973496e523563434936496b705856434a392e65794a756232356a5a534936496a4d774d57566d5a5467344c5755324d6a49744e444d30595330344e4459324c5467774e57517a4e5751345a54466a59694973496d39795a4756795357526c626e52705a6d6c6c63694936496a51775a6a51354f4445794c5445794d3249744e4751784d4331695a47466c4c5455324d5755305a575179596a6b354d434973496d6c68644349364d5463344d54637a4d6a6b794f58302e5a6f6b68766e30626e796a4c734f70754a797a5131666e73465a486b4d36456d3965724c7a6678576443492d2d2d2d2d2d454e44204d454d4f2d2d2d2d2d2dc0",
"transactionParameters": {
"amount": "1000000",
"memo": "------BEGIN MEMO------eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJub25jZSI6IjMwMWVmZTg4LWU2MjItNDM0YS04NDY2LTgwNWQzNWQ4ZTFjYiIsIm9yZGVySWRlbnRpZmllciI6IjQwZjQ5ODEyLTEyM2ItNGQxMC1iZGFlLTU2MWU0ZWQyYjk5MCIsImlhdCI6MTc4MTczMjkyOX0.Zokhvn0bnyjLsOpuJyzQ1fnsFZHkM6Em9erLzfxWdCI------END MEMO------"
}
},
"receiptEmail": "alice@example.com"
},
"legal": {
"requirements": [
{ "display": "show-checkbox", "type": "crossmint-terms-of-service", "url": "https://staging.crossmint.com/legal/crossmint-terms-of-service/TRGUSAALLALLALL" },
{ "display": "show-text", "type": "crossmint-privacy-policy", "url": "https://staging.crossmint.com/legal/crossmint-privacy-policy/ALLALLALLALLALL" }
]
}
}
}
```
The order's `payment.preparation.serializedTransaction` is a prepared USDC transfer that already carries the order's memo in its calldata. Sign and broadcast it from the payer wallet. Crossmint matches the deposit to the order by that memo automatically, so no further call is needed.
Sign and send it from the wallet you created, using its `server` signer (a gasless transaction):
```typescript theme={null}
import { EVMWallet } from "@crossmint/wallets-sdk";
import { parseTransaction } from "viem";
// `wallet` from the setup step, `order` from the create-order step
await wallet.useSigner({ type: "server", secret: process.env.CROSSMINT_SIGNER_SECRET });
const { to, data } = parseTransaction(order.payment.preparation.serializedTransaction);
await EVMWallet.from(wallet).sendTransaction({ to, value: 0n, data });
```
See [Send a Transaction](/wallets/guides/send-transaction-evm) for signer setup and other platforms.
Sign and broadcast it with the payer's key using viem:
```typescript theme={null}
import { createWalletClient, http, parseTransaction } from "viem";
import { privateKeyToAccount } from "viem/accounts";
import { baseSepolia } from "viem/chains";
const account = privateKeyToAccount(process.env.PAYER_PRIVATE_KEY);
const walletClient = createWalletClient({ account, chain: baseSepolia, transport: http() });
// `order` from the create-order step
const { to, data } = parseTransaction(order.payment.preparation.serializedTransaction);
const hash = await walletClient.sendTransaction({ to, value: 0n, data });
```
Poll the order until the payout completes, or subscribe to webhooks (see [Manage Orders](/offramp/guides/manage-orders)).
```bash theme={null}
curl -X GET 'https://staging.crossmint.com/api/2022-06-09/orders/' \
-H 'X-API-KEY: '
```
`GET` returns the order flat at the top level (no `order` wrapper and no `clientSecret`, unlike the create response). Watch `phase`, which moves from `payment` to `completed`, along with `payment.status` and `lineItems[0].delivery.status`. When it completes, `payment.received` is populated with the matched on-chain transaction.
```json theme={null}
{
"orderId": "40f49812-123b-4d10-bdae-561e4ed2b990",
"phase": "completed",
"locale": "en-US",
"lineItems": [
{
"metadata": {
"name": "USD",
"description": "United States Dollar",
"imageUrl": "https://www.crossmint.com/assets/ui/flags/us.svg"
},
"quote": {
"status": "valid",
"charges": {
"unit": { "amount": "1", "currency": "usdc" },
"fees": { "type": "exact", "amount": "0.002", "currency": "usdc" }
},
"totalPrice": { "amount": "1", "currency": "usdc" },
"quantityRange": { "lowerBound": "1", "upperBound": "1" }
},
"delivery": {
"status": "completed",
"recipient": {
"locator": "paymentMethodId:d5e5c48a-7dec-44b1-8cf4-4615bf00c8fc",
"paymentMethodId": "d5e5c48a-7dec-44b1-8cf4-4615bf00c8fc"
}
},
"executionMode": "exact-in",
"callData": {
"mode": "exact-in",
"amount": "1",
"outFiatCurrency": "usd",
"effectiveAmount": "0.998"
},
"quantity": 1
}
],
"quote": {
"status": "valid",
"quotedAt": "2026-06-17T21:48:49.059Z",
"expiresAt": "2026-06-17T21:58:49.059Z",
"totalPrice": { "amount": "1", "currency": "usdc" }
},
"payment": {
"status": "completed",
"method": "base-sepolia",
"currency": "usdc",
"preparation": {
"chain": "base-sepolia",
"payerAddress": "0xAa266270cf90FEEA9760617d9c7B5083e9f08984",
"serializedTransaction": "0x02f9018e83014a348083171dd9837ec1d582c3be94036cbd53842c5426634e7929541ec2318f3dcf7e80b90164a9059cbb000000000000000000000000bd1af733aed9d9f472a15a4ce13f42097bbc9eaa00000000000000000000000000000000000000000000000000000000000f42400a0a2d2d2d2d2d2d424547494e204d454d4f2d2d2d2d2d2d65794a68624763694f694a49557a49314e694973496e523563434936496b705856434a392e65794a756232356a5a534936496a4d774d57566d5a5467344c5755324d6a49744e444d30595330344e4459324c5467774e57517a4e5751345a54466a59694973496d39795a4756795357526c626e52705a6d6c6c63694936496a51775a6a51354f4445794c5445794d3249744e4751784d4331695a47466c4c5455324d5755305a575179596a6b354d434973496d6c68644349364d5463344d54637a4d6a6b794f58302e5a6f6b68766e30626e796a4c734f70754a797a5131666e73465a486b4d36456d3965724c7a6678576443492d2d2d2d2d2d454e44204d454d4f2d2d2d2d2d2dc0",
"transactionParameters": {
"amount": "1000000",
"memo": "------BEGIN MEMO------eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJub25jZSI6IjMwMWVmZTg4LWU2MjItNDM0YS04NDY2LTgwNWQzNWQ4ZTFjYiIsIm9yZGVySWRlbnRpZmllciI6IjQwZjQ5ODEyLTEyM2ItNGQxMC1iZGFlLTU2MWU0ZWQyYjk5MCIsImlhdCI6MTc4MTczMjkyOX0.Zokhvn0bnyjLsOpuJyzQ1fnsFZHkM6Em9erLzfxWdCI------END MEMO------"
}
},
"received": {
"chain": "base-sepolia",
"txId": "0xc9537a0e4ee4794101514d30418bd20f09cf66c3ce86b82b0be916489a338e85",
"amount": "1",
"currency": "usdc"
},
"receiptEmail": "alice@example.com"
}
}
```
On staging the payout is simulated. To pay out to a bank in production, see [Launch in Production](/offramp/guides/going-to-production).
## Troubleshooting
Offramp's KYC endpoints are turned on per project. Contact sales to enable KYC for yours. The response `project not configured to support the users KYC endpoints` is the signal to reach out.
The user and KYC endpoints expect a `userId:` locator, for example `userId:alice-123`. The `email:` and other locator formats apply to Crossmint auth wallets, so use `userId:` for these endpoints.
Verification is asynchronous. Poll `GET /users/{userId}/identity-verification` until the `offramp` eligibility reads `verified`, then create the order.
Create the payer wallet with `owner: "userId:"`. With no owner it is company-owned, which the order does not accept. A wallet's owner is set once at creation.
After you broadcast the prepared transaction, Crossmint matches it to the order by its memo, so the deposit is already assigned. You do not need to call `POST /orders/{orderId}/payment`.
## Next steps
Status, webhooks, and failures
The end-to-end offramp flow
# Payment Methods
Source: https://docs.crossmint.com/onramp/concepts/payment-methods
Understanding the payment methods available for onramp
Crossmint Onramp lets users buy stablecoins with the payment methods they already have: debit and credit cards, Apple Pay, and Google Pay. This page explains how each method works, where it is available, and why an onramp transaction is different from a token checkout.
## Supported Payment Methods
| Method | Description | Availability |
| :------------- | :--------------------------------------------------- | :--------------------------------------------------------- |
| **Card** | Debit and credit cards (Visa, Mastercard) | All platforms |
| **Apple Pay** | One-click payment with cards stored in Apple Wallet | Safari on macOS and iOS (web), native iOS apps |
| **Google Pay** | One-click payment with cards stored in Google Wallet | Web browsers, Android apps (React Native, Flutter, Kotlin) |
All three methods are selected by the user inside the same embedded checkout component. You control which methods are offered through a single configuration option (`allowedMethods`), and the component only displays the methods that the user's device and browser actually support. For example, Apple Pay is never shown on an Android device, even when it is enabled.
## How Each Method Works
### Card
The user enters their card details directly in the embedded checkout. Card data is captured in a PCI-compliant environment and never touches your servers. Cards are the baseline method: they work on every platform without additional configuration.
### Apple Pay
Apple Pay wraps the user's stored cards behind Face ID, Touch ID, or a device passcode. Instead of typing card details, the user confirms the payment through the native Apple Pay sheet.
Apple requires every web domain that displays the Apple Pay button to be verified with Apple. Crossmint manages the Apple merchant relationship, so you only need to verify your domain once through the Crossmint Console. Native iOS apps do not require domain verification; the Swift SDK supports Apple Pay automatically.
### Google Pay
Google Pay works the same way for cards stored in Google Wallet: the user confirms the payment through the native Google Pay sheet instead of typing card details.
On the web, Google Pay works without additional configuration. Mobile apps require native configuration (permissions and intent declarations) because the payment sheet is rendered by the operating system, and production Android apps must be approved by Google before Google Pay is shown to real users.
## Why Onramp Is Not a Standard Checkout
A standard card payment in a checkout pays for a good or service; the cardholder receives a product, and the transaction ends there. An onramp transaction is different: the user's fiat is converted into stablecoins that are delivered to a wallet. This is a **regulated transfer** — a money movement that must run on licensed infrastructure.
Because of this, every onramp transaction is routed through Crossmint's regulated infrastructure, which handles:
* **Licensing** — Crossmint operates the required licenses, so you do not need your own.
* **Identity verification** — users complete a lightweight KYC once, valid for up to \$1,000 per day. See [User Onboarding](/onramp/introduction/user-onboarding).
* **AML screening and sanctions checks** — every transaction is screened before the stablecoins are delivered.
* **Chargeback liability** — Crossmint assumes liability for fraudulent disputes.
The payment method only changes how the user funds the transaction. Whether they pay with a card, Apple Pay, or Google Pay, the compliance flow and the on-chain delivery are identical.
The user experience differs mainly in friction: Apple Pay and Google Pay skip manual card entry and typically convert better on mobile devices, while cards remain the universal fallback.
## Choosing Which Methods to Offer
Offering every method is usually the right default: the checkout only shows what the device supports, so enabling all three costs nothing. Restricting methods makes sense when:
* Your app is native-only on one platform and you want to hide methods that will never appear.
* You have a business reason to route users through a specific method.
## See Also
Enable Apple Pay, Google Pay, and cards in your integration
How identity verification works for onramp
# Import User KYC Data
Source: https://docs.crossmint.com/onramp/guides/import-user-kyc-data
Use existing KYC data for faster onboarding
If you already verify users with an identity verification (IDV) provider like Sumsub or Persona, you can send that data to Crossmint before creating an onramp order. When the data is complete, users can skip in-checkout identity verification.
If you prefer to have individual users provide their KYC data client-side, Crossmint supports that as well. See the [React Quickstart](/onramp/quickstarts/react) for that approach.
## How It Works
1. Verify the user with your IDV provider
2. Have the user accept Crossmint's Privacy Policy
3. [Register the user's KYC data](/identity/register-user-data) with Crossmint
4. Create an onramp order to the wallet linked to that user
5. Crossmint runs sanctions, Politically Exposed Persons (PEP), and compliance checks
6. The user completes payment
## Accept Crossmint's Privacy Policy
Before sharing any user KYC data with Crossmint, the user must accept Crossmint's Privacy Policy. Record their acceptance with the [Accept Legal Document](/api-reference/users/accept-legal-document) endpoint **before** registering KYC data:
```bash cURL theme={null}
curl --request PUT \
--url 'https://staging.crossmint.com/api/2025-06-09/users/userId:YOUR_USER_ID/legal-documents' \
--header 'X-API-KEY: ' \
--header 'Content-Type: application/json' \
--data '{
"type": "crossmint-privacy-policy",
"acceptedAt": "2025-10-05T14:48:00Z"
}'
```
See the [Identity Quickstart](/identity/quickstart) for more details on the privacy policy and the users endpoints.
## Create an Onramp Order
Once the user is registered with their KYC data, you can create an onramp order. The order will use the registered KYC data for compliance checks.
**Important**: Crossmint links pre-registered KYC data to an order using the user's email address. The `payment.receiptEmail` value must match the email used when registering the user's KYC data with the [Create User API](/api-reference/users/upsert-user).
```js Node.js theme={null}
const options = {
method: 'POST',
headers: {
'X-API-KEY': 'YOUR_API_KEY',
'Content-Type': 'application/json'
},
body: JSON.stringify({
recipient: {
walletAddress: "YOUR_WALLET_ADDRESS"
},
payment: {
method: "card",
receiptEmail: "YOUR_USER_EMAIL"
},
lineItems: [
{
tokenLocator: "base-sepolia:0x036CbD53842c5426634e7929541eC2318f3dCF7e",
executionParameters: {
mode: "exact-in",
amount: "1"
}
}
]
})
};
fetch('https://staging.crossmint.com/api/2022-06-09/orders', options)
.then(response => response.json())
.then(response => console.log(response))
.catch(err => console.error(err));
```
```bash cURL theme={null}
curl --request POST \
--url https://staging.crossmint.com/api/2022-06-09/orders \
--header 'X-API-KEY: YOUR_API_KEY' \
--header 'Content-Type: application/json' \
--data '{
"recipient": {
"walletAddress": "YOUR_WALLET_ADDRESS"
},
"payment": {
"method": "card",
"receiptEmail": "YOUR_USER_EMAIL"
},
"lineItems": [
{
"tokenLocator": "base-sepolia:0x036CbD53842c5426634e7929541eC2318f3dCF7e",
"executionParameters": {
"mode": "exact-in",
"amount": "1"
}
}
]
}'
```
```python Python theme={null}
import requests
url = "https://staging.crossmint.com/api/2022-06-09/orders"
payload = {
"recipient": {
"walletAddress": "YOUR_WALLET_ADDRESS"
},
"payment": {
"method": "card",
"receiptEmail": "YOUR_USER_EMAIL"
},
"lineItems": [
{
"tokenLocator": "base-sepolia:0x036CbD53842c5426634e7929541eC2318f3dCF7e",
"executionParameters": {
"mode": "exact-in",
"amount": "1"
}
}
]
}
headers = {
"X-API-KEY": "YOUR_API_KEY",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.json())
```
The `lineItems` field accepts either a single object or an array. The examples above use an array for consistency, but you can also pass a single line item object directly.
### Linking KYC Data to Orders
When you create an order, Crossmint automatically looks up the user's KYC data based on the email address provided in `payment.receiptEmail`. Here is the complete flow:
Record the user's privacy-policy acceptance first (see the **Accept Crossmint's Privacy Policy** section above). This is required before any KYC data is shared with Crossmint.
First, register the user's KYC information using the [Create User API](/api-reference/users/upsert-user). Make sure to include the user's email in the `email` field within `kycData`:
```javascript theme={null}
const userLocator = "userId:YOUR_USER_ID";
const options = {
method: 'PUT',
headers: {
'X-API-KEY': 'YOUR_API_KEY',
'Content-Type': 'application/json'
},
body: JSON.stringify({
userDetails: {
firstName: "John",
lastName: "Doe",
dateOfBirth: "1995-01-01",
countryOfResidence: "DE"
},
kycData: {
nationality: "DE",
addressOfResidence: {
line1: "123 Hauptstrasse",
line2: "Apt 5",
city: "Berlin",
stateOrRegion: "Berlin",
postalCode: "10115"
},
email: "YOUR_USER_EMAIL",
identityDocument: {
type: "passport",
number: "AS12321",
issuingCountryCode: "DE"
},
ipAddresses: ["203.0.113.1"]
}
})
};
fetch(`https://staging.crossmint.com/api/2025-06-09/users/${userLocator}`, options)
.then(response => response.json())
.then(response => console.log(response))
.catch(err => console.error(err));
```
When creating the onramp order, use the same email address in `payment.receiptEmail`:
```javascript theme={null}
const options = {
method: 'POST',
headers: {
'X-API-KEY': 'YOUR_API_KEY',
'Content-Type': 'application/json'
},
body: JSON.stringify({
recipient: {
walletAddress: "YOUR_WALLET_ADDRESS"
},
payment: {
method: "card",
receiptEmail: "YOUR_USER_EMAIL"
},
lineItems: [
{
tokenLocator: "base-sepolia:0x036CbD53842c5426634e7929541eC2318f3dCF7e",
executionParameters: {
mode: "exact-in",
amount: "1"
}
}
]
})
};
fetch('https://staging.crossmint.com/api/2022-06-09/orders', options)
.then(response => response.json())
.then(response => console.log(response))
.catch(err => console.error(err));
```
## Order Responses
When all required KYC data has been provided and validated, the order moves to the payment phase:
```json theme={null}
{
"clientSecret": "...",
"order": {
"orderId": "987e81ab-8c8f-464e-95e9-11ceda80d559",
"phase": "payment",
"locale": "en-US",
"lineItems": [...],
"quote": {...},
"payment": {
"method": "card",
"currency": "usd",
"status": "awaiting-payment"
}
}
}
```
When full KYC data is present but Crossmint needs to run additional compliance checks, the order enters a processing state. This typically resolves within a few seconds as Crossmint runs the appropriate checks:
```json theme={null}
{
"clientSecret": "...",
"order": {
"orderId": "987e81ab-8c8f-464e-95e9-11ceda80d559",
"phase": "payment",
"locale": "en-US",
"lineItems": [...],
"quote": {...},
"payment": {
"method": "card",
"currency": "usd",
"status": "pending-kyc-review"
}
}
}
```
If no user personal data is provided, or if the data is insufficient, the order will fail:
```json theme={null}
{
"error": true,
"message": "Required full KYC user data is missing to complete the order"
}
```
## Guides
# Onramp to Non-Crossmint Wallets
Source: https://docs.crossmint.com/onramp/guides/onramp-to-external-wallets
Enable onramp orders to wallets not powered by Crossmint
This guide walks you through how to enable onramp orders to external wallets.
**Internal vs. external wallets:** When you create a Crossmint wallet for a user, set the [`owner`](/wallets/guides/create-wallet) to that user's locator so the wallet is recognized as theirs — onramps to those wallets do not require ownership verification. A wallet created without an `owner`, or any wallet not created through Crossmint, is treated as **external**: you must link it to the user and verify ownership, as shown below.
Before creating an onramp order to an external wallet, you must link the wallet to a Crossmint user (Crossmint creates the user automatically if the locator does not exist yet), using the Link External Wallet API:
```bash cURL theme={null}
curl --request PUT \
--url https://staging.crossmint.com/api/2025-06-09/users/{userLocator}/linked-wallets/{address} \
--header 'X-API-KEY: ' \
--header 'Content-Type: application/json' \
--data '{
"chain": "base-sepolia",
"proof": ""
}'
```
```js Node.js theme={null}
const options = {
method: 'PUT',
headers: {
'X-API-KEY': '',
'Content-Type': 'application/json'
},
body: JSON.stringify({
chain: "base-sepolia",
proof: ""
})
};
const userLocator = "email:user@example.com";
const walletAddress = "0x1234567890abcdef1234567890abcdef12345678";
fetch(`https://staging.crossmint.com/api/2025-06-09/users/${userLocator}/linked-wallets/${walletAddress}`, options)
.then(response => response.json())
.then(response => console.log(response))
.catch(err => console.error(err));
```
The `proof` field is optional:
* If you omit it, you are simply linking the external wallet to the user without proving ownership yet.
* If you pass it, you prove ownership of the wallet in the same request.
When linking without proof, the API responds with a verification challenge:
```json theme={null}
{
"address": "0x1234567890abcdef1234567890abcdef12345678",
"chain": "base-sepolia",
"type": "external-wallet",
"ownership": {
"verified": false,
"verificationChallenge": "crossmint.com wants you to sign in with your blockchain account:\n0x1234...5678\n\nI am signing this message to prove ownership of my wallet address 0x1234...5678 for Crossmint verification.\n\nURI: https://..."
}
}
```
The `verificationChallenge` follows the [CAIP-122](https://chainagnostic.org/CAIPs/caip-122) standard.
Create an onramp order using the [Create Order API](/api-reference/headless/create-order) with the linked external wallet address as the recipient.
Depending on the transaction value and the user's past activity, the order may require wallet ownership verification (steps 3 and 4). If verification is not required, the order proceeds directly to the KYC/payment phase and you can skip to step 5.
```bash cURL theme={null}
curl --request POST \
--url https://staging.crossmint.com/api/2022-06-09/orders \
--header 'X-API-KEY: ' \
--header 'Content-Type: application/json' \
--data '{
"recipient": {
"walletAddress": "0x1234567890abcdef1234567890abcdef12345678"
},
"payment": {
"method": "card",
"receiptEmail": "user@example.com"
},
"lineItems": [
{
"tokenLocator": "base-sepolia:0x036CbD53842c5426634e7929541eC2318f3dCF7e",
"executionParameters": {
"mode": "exact-in",
"amount": "10"
}
}
]
}'
```
```js Node.js theme={null}
const options = {
method: 'POST',
headers: {
'X-API-KEY': '',
'Content-Type': 'application/json'
},
body: JSON.stringify({
recipient: {
walletAddress: "0x1234567890abcdef1234567890abcdef12345678"
},
payment: {
method: "card",
receiptEmail: "user@example.com"
},
lineItems: [
{
tokenLocator: "base-sepolia:0x036CbD53842c5426634e7929541eC2318f3dCF7e",
executionParameters: {
mode: "exact-in",
amount: "10"
}
}
]
})
};
fetch('https://staging.crossmint.com/api/2022-06-09/orders', options)
.then(response => response.json())
.then(response => console.log(response))
.catch(err => console.error(err));
```
The order may or may not require wallet ownership verification:
* **Below threshold**: If the transaction value is below **$1,000** **and** the user's past 30-day onramp transaction volume is below **$1,000**, ownership verification is not required. The order proceeds directly to the KYC/payment phase.
* **Above threshold**: If either threshold is surpassed, the order status will be `requires-recipient-verification` and the user must sign a message to prove wallet ownership (steps 3 and 4).
When ownership verification is required, the order response will look like this:
```json theme={null}
{
"clientSecret": "...",
"order": {
"orderId": "987e81ab-8c8f-464e-95e9-11ceda80d559",
"phase": "payment",
"lineItems": [...],
"quote": {...},
"payment": {
"status": "requires-recipient-verification",
"method": "card",
"currency": "usd",
"preparation": {
"message": "crossmint.com wants you to sign in with your blockchain account:\n0x1234...5678\n\nI am signing this message to prove ownership of my wallet address 0x1234...5678 for Crossmint verification.\n\nURI: https://...\nVersion: 1\nNonce: ...\nIssued At: ...\nExpiration Time: ...\nRequest ID: ...\nChain ID: base-sepolia"
}
}
}
}
```
This step is only required if the order status is `requires-recipient-verification`.
The user must sign the `preparation.message` returned in the order response using their wallet's private key.
```js EVM (ethers.js) theme={null}
import { Wallet } from "ethers";
const PRIVATE_KEY = "YOUR_WALLET_PRIVATE_KEY"; // Replace with your wallet's private key
const wallet = new Wallet(PRIVATE_KEY);
const message = order.payment.preparation.message;
const signature = await wallet.signMessage(message);
```
```js Solana (tweetnacl) theme={null}
import nacl from "tweetnacl";
import { decodeUTF8 } from "tweetnacl-util";
import { encode as base64Encode } from "@stablelib/base64";
const message = order.payment.preparation.message;
const messageBytes = decodeUTF8(message);
const signatureBytes = nacl.sign.detached(messageBytes, keypair.secretKey);
const signature = base64Encode(signatureBytes);
```
This step is only required if the order status is `requires-recipient-verification`.
Use the same Link External Wallet API to submit the signature as proof of ownership. Pass the signature in the `proof` field:
```bash cURL theme={null}
curl --request PUT \
--url https://staging.crossmint.com/api/2025-06-09/users/{userLocator}/linked-wallets/{address} \
--header 'X-API-KEY: ' \
--header 'Content-Type: application/json' \
--data '{
"chain": "base-sepolia",
"proof": ""
}'
```
```js Node.js theme={null}
const options = {
method: 'PUT',
headers: {
'X-API-KEY': '',
'Content-Type': 'application/json'
},
body: JSON.stringify({
chain: "base-sepolia",
proof: ""
})
};
const userLocator = "email:user@example.com";
const walletAddress = "0x1234567890abcdef1234567890abcdef12345678";
fetch(`https://staging.crossmint.com/api/2025-06-09/users/${userLocator}/linked-wallets/${walletAddress}`, options)
.then(response => response.json())
.then(response => console.log(response))
.catch(err => console.error(err));
```
Once ownership is verified, the response will confirm verification:
```json theme={null}
{
"address": "0x1234567890abcdef1234567890abcdef12345678",
"chain": "base-sepolia",
"type": "external-wallet",
"ownership": {
"verified": true
}
}
```
Once ownership is verified (or if verification was not required), fetch the order using the [Get Order API](/api-reference/headless/get-order). The order status should now have proceeded to the next phase:
* **`requires-kyc`**: The user hasn't completed KYC yet and will be guided through the KYC flow first.
* **`awaiting-payment`**: The user has completed KYC and can proceed to complete payment.
The user will be guided through Crossmint's embedded components to complete their order, handling payment and any remaining compliance steps automatically.
# Add Payment Methods
Source: https://docs.crossmint.com/onramp/guides/payment-methods
Enable cards, Apple Pay, and Google Pay for onramp
This guide covers how to enable each supported payment method — cards, Apple Pay, and Google Pay — in your onramp integration, including the platform-specific configuration each one requires.
All payment methods are controlled through the payment configuration of the checkout component. The checkout only displays methods that the user's device supports, so enabling a method is safe even on platforms where it cannot appear. For an explanation of how each method works, see [Payment Methods](/onramp/concepts/payment-methods).
## Prerequisites
* A working onramp integration — complete a [quickstart](/onramp/quickstarts/react) first.
## Configure Allowed Methods
Set the methods you want to offer in the fiat payment configuration:
```jsx theme={null}
```
```jsx theme={null}
```
```dart theme={null}
CrossmintEmbeddedCheckout(
apiKey: clientApiKey,
config: CrossmintCheckoutConfig(
order: CrossmintCheckoutExistingOrder(
orderId: order.orderId,
clientSecret: order.clientSecret,
),
payment: CrossmintCheckoutPayment(
crypto: CrossmintCheckoutCryptoPayment(enabled: false),
fiat: CrossmintCheckoutFiatPayment(
enabled: true,
allowCard: true,
allowApplePay: true,
allowGooglePay: true,
),
),
),
)
```
```kotlin theme={null}
CrossmintEmbeddedCheckout(
orderId = "YOUR_ORDER_ID",
clientSecret = "YOUR_CLIENT_SECRET",
payment = CheckoutPayment(
crypto = CheckoutCryptoPayment(enabled = false),
fiat = CheckoutFiatPayment(
enabled = true,
allowedMethods = CheckoutAllowedMethods(
card = true,
applePay = true,
googlePay = true
)
)
),
environment = CheckoutEnvironment.STAGING
)
```
```swift theme={null}
CrossmintEmbeddedCheckout(
orderId: "YOUR_ORDER_ID",
clientSecret: "YOUR_CLIENT_SECRET",
payment: CheckoutPayment(
crypto: CheckoutCryptoPayment(enabled: false),
fiat: CheckoutFiatPayment(
enabled: true,
allowedMethods: CheckoutAllowedMethods(
googlePay: false,
applePay: true,
card: true
)
)
)
)
```
Cards work on every platform with no additional setup. Apple Pay and Google Pay require the platform-specific configuration below.
## Apple Pay
To display Apple Pay on your website (Safari on macOS and iOS), you must verify your domain with Apple through the Crossmint Console. This is a one-time setup per domain.
### Prerequisites
1. **HTTPS hosting** — your site must be served over HTTPS.
2. **Crossmint account** — you need an active Crossmint developer account.
Go to the Apple Pay Domains page in the Crossmint Console and click **Download file** to get the Apple Developer Merchant ID Domain Association file.
Host the downloaded file on your server at the following path:
```text theme={null}
https://YOUR_DOMAIN/.well-known/apple-developer-merchantid-domain-association
```
**Option 1: Using the Public Folder**
1. Create the `.well-known` folder inside your `public` directory.
2. Place the downloaded file in `public/.well-known/` with the filename `apple-developer-merchantid-domain-association` (no extension).
**Option 2: Using a Route**
1. Create a folder at `app/.well-known/apple-developer-merchantid-domain-association/`
2. Add a `route.ts` file with the following content:
```typescript route.ts theme={null}
import { NextResponse } from "next/server";
export async function GET() {
return new NextResponse("PASTE_FILE_CONTENT_HERE", {
headers: {
"Content-Type": "text/plain",
},
});
}
```
Replace `PASTE_FILE_CONTENT_HERE` with the content of the downloaded verification file.
1. Create the `.well-known` folder inside your `public` directory.
2. Place the downloaded file in `public/.well-known/` with the filename `apple-developer-merchantid-domain-association` (no extension).
1. Create the `.well-known` folder inside your `public` directory.
2. Place the downloaded file in `public/.well-known/` with the filename `apple-developer-merchantid-domain-association` (no extension).
1. Return to the Apple Pay Domains page in the Crossmint Console.
2. Enter your domain (e.g., `example.com` or `checkout.example.com`) in the input field.
3. Click **Verify domain**.
If verification succeeds, your domain will appear in the list of registered domains and Apple Pay will be available as a payment method in the onramp checkout.
If verification fails, ensure the file is publicly accessible at the correct path. You can test by visiting `https://YOUR_DOMAIN/.well-known/apple-developer-merchantid-domain-association` in your browser.
### Local Testing
Using ngrok is recommended for local testing:
1. Start ngrok with a fixed domain if possible for consistent testing.
2. Host the verification file on your local server.
3. Register the ngrok domain in the Crossmint Console.
4. Test Apple Pay on Safari (macOS) or any iOS device.
Apple Pay is supported automatically in native iOS apps via the Swift SDK — no domain verification is required. Enable it through `allowedMethods` as shown in [Configure Allowed Methods](#configure-allowed-methods), and see the [Swift Quickstart](/onramp/quickstarts/swift) for the full integration.
## Google Pay
On the web, Google Pay works out of the box — enabling it via `allowedMethods` is all that is needed. Mobile apps require additional native configuration because the payment sheet is rendered by the operating system.
### Prerequisites
* React Native project with Expo or bare workflow
* Android device or emulator with Google Play Services
```shell theme={null}
pnpm add @crossmint/client-sdk-react-native-ui
```
The SDK includes `react-native-webview` as a dependency. If you need to install it separately, ensure version **13.15.0 or higher**:
```shell theme={null}
pnpm add react-native-webview@^13.15.0
```
Update your `app.json` to enable Google Pay:
```json theme={null}
{
"expo": {
"plugins": [
[
"@crossmint/client-sdk-react-native-ui",
{
"enableGooglePay": true
}
]
]
}
}
```
This plugin modifies native Android permissions required for Google Pay. The app must be rebuilt after adding this configuration.
Hot reload cannot apply the permission changes. You must rebuild:
```shell theme={null}
npx expo run:android
```
Or for bare React Native:
```shell theme={null}
npx react-native run-android
```
Set `googlePay: true` in `allowedMethods` as shown in [Configure Allowed Methods](#configure-allowed-methods).
### Prerequisites
* Minimum Android SDK version 24 (Android 7.0)
* Jetpack Compose
In your app-level `build.gradle.kts`:
```kotlin theme={null}
dependencies {
implementation("com.crossmint:crossmint-sdk-android:0.0.9")
}
```
Add the required query intents to enable Google Pay functionality:
```xml theme={null}
```
Set `googlePay = true` in `allowedMethods` as shown in [Configure Allowed Methods](#configure-allowed-methods).
### Google Pay Production Approval
Google Pay works immediately in **staging** environments. For **production**, Google requires explicit approval of your Android app.
1. Navigate to the Google Pay & Wallet Console
2. Select "Merchant" as your business type
3. Complete all business profile information
4. Navigate to **Google Pay API** and click "Get Started"
5. Accept the Google Pay API Terms of Service
6. Note your **Merchant ID** (top-right corner after completion)
In the Google Pay & Wallet Console:
1. Navigate to **Google Pay API** then **Integrations** then **Integrate with your Android app**
2. Locate your Android application and click "Manage"
3. Select your integration type (typically "Gateway")
4. Upload screenshots of your TEST Google Pay integration
5. Click "Save" then "Submit for approval"
**Required Screenshots:**
* Product/item selection showing Google Pay option
* Cart or checkout view with payment options
* Google Pay payment sheet with card selection
* Confirmation or receipt screen
After Google approval (typically \~1 business day):
1. Sign your APK with a **release key** (debug keys do not work in production)
2. Update environment settings:
* Kotlin: `environment = CheckoutEnvironment.PRODUCTION`
* React Native: Use production API key
3. Publish your app to Google Play Store
## Troubleshooting
1. **Domain not verified (web):** Verify your domain on the Apple Pay Domains page
2. **Unsupported browser:** Apple Pay on web only appears in Safari on macOS and iOS
3. **Payment method disabled:** Check `applePay: true` in `allowedMethods`
1. **Plugin not configured:** Verify `enableGooglePay: true` in `app.json` plugins
2. **App not rebuilt:** Run `npx expo run:android` after adding the plugin
3. **Environment mismatch:** Staging works without approval; production requires it
4. **Payment method disabled:** Check `googlePay: true` in `allowedMethods`
1. **AndroidManifest missing queries:** Add all three Chromium intent queries
2. **Native rebuild required:** Hot reload cannot apply permission changes
3. **Device requirements:** User needs Google Play Services 25.18.30+ and WebView 137+
## Next Steps
Understand how each payment method works and why onramp is a regulated transfer
Customize the onramp checkout UI to match your brand identity
Register users and handle KYC before their first onramp transaction
Build a complete onramp integration from scratch
# Proof of Ownership
Source: https://docs.crossmint.com/onramp/guides/proof-of-ownership
Understand when and how wallet ownership verification works for onramp transactions
When onramping to non-Crossmint wallets, the onramp may require the user to prove they own the recipient wallet by signing a message. This is a legal requirement under anti-money laundering (AML) and counter-terrorism financing (CTF) regulations, which mandate verification of the beneficiary wallet owner's identity above certain transaction thresholds. This page explains when proof of ownership is required and how it works.
## When Is Proof of Ownership Required?
Proof of ownership is triggered based on transaction volume thresholds:
| Condition | Verification Required? |
| :---------------------------------------------------------------- | :--------------------- |
| Transaction below **\$1,000** AND 30-day volume below **\$1,000** | No |
| Transaction above **\$1,000** OR 30-day volume above **\$1,000** | Yes |
Proof of ownership only applies to **external (non-Crossmint) wallets**. A Crossmint-managed wallet does not require ownership verification **as long as it was created with its `owner` set to the user**. A Crossmint wallet created without an `owner` (or owned by someone else) is treated as external and must be linked and verified like any other external wallet.
Wallet-ownership proof applies only to users verified outside the United States. Buyers whose KYC country of residence is the United States are not asked to prove wallet ownership, regardless of transaction amount.
## How It Works
Before creating an onramp order to an external wallet, link the wallet to a Crossmint user (created automatically if one does not yet exist for the locator) using the Link External Wallet API:
```bash theme={null}
curl --request PUT \
--url https://staging.crossmint.com/api/2025-06-09/users/YOUR_USER_LOCATOR/linked-wallets/YOUR_WALLET_ADDRESS \
--header 'X-API-KEY: YOUR_API_KEY' \
--header 'Content-Type: application/json' \
--data '{
"chain": "base-sepolia"
}'
```
The API responds with a verification challenge following the CAIP-122 standard:
```json theme={null}
{
"address": "0x1234...5678",
"chain": "base-sepolia",
"type": "external-wallet",
"ownership": {
"verified": false,
"verificationChallenge": "crossmint.com wants you to sign in with your blockchain account:\n0x1234...5678\n\n..."
}
}
```
Create the onramp order as usual. If verification is required, the order response will have status `requires-recipient-verification`:
```json theme={null}
{
"order": {
"payment": {
"status": "requires-recipient-verification",
"preparation": {
"message": "crossmint.com wants you to sign in with your blockchain account:\n0x1234...5678\n\n..."
}
}
}
}
```
Have the user sign the `preparation.message` using their wallet's private key:
```js EVM (ethers.js) theme={null}
import { Wallet } from "ethers";
const wallet = new Wallet("YOUR_PRIVATE_KEY");
const message = order.payment.preparation.message;
const signature = await wallet.signMessage(message);
```
```js Solana (tweetnacl) theme={null}
import nacl from "tweetnacl";
import { decodeUTF8 } from "tweetnacl-util";
import { encode as base64Encode } from "@stablelib/base64";
const YOUR_SECRET_KEY_BYTES = new Uint8Array(64); // Replace with your actual secret key bytes
const keypair = nacl.sign.keyPair.fromSecretKey(YOUR_SECRET_KEY_BYTES);
const message = order.payment.preparation.message;
const messageBytes = decodeUTF8(message);
const signatureBytes = nacl.sign.detached(messageBytes, keypair.secretKey);
const signature = base64Encode(signatureBytes);
```
Submit the signature as proof of ownership using the same Link External Wallet API:
```bash theme={null}
curl --request PUT \
--url https://staging.crossmint.com/api/2025-06-09/users/YOUR_USER_LOCATOR/linked-wallets/YOUR_WALLET_ADDRESS \
--header 'X-API-KEY: YOUR_API_KEY' \
--header 'Content-Type: application/json' \
--data '{
"chain": "base-sepolia",
"proof": "YOUR_SIGNATURE"
}'
```
Once verified, the response confirms ownership:
```json theme={null}
{
"address": "0x1234...5678",
"chain": "base-sepolia",
"type": "external-wallet",
"ownership": {
"verified": true
}
}
```
After ownership is verified, fetch the order using the [Get Order API](/api-reference/headless/get-order). The status will advance to:
* **`requires-kyc`** — the user has not completed KYC yet.
* **`awaiting-payment`** — the user can proceed to payment.
## Pre-Verifying Ownership
You can verify wallet ownership at the time of linking (before any order is created) by including the `proof` field in the initial Link External Wallet call. This avoids requiring verification during the checkout flow.
```bash theme={null}
curl --request PUT \
--url https://staging.crossmint.com/api/2025-06-09/users/YOUR_USER_LOCATOR/linked-wallets/YOUR_WALLET_ADDRESS \
--header 'X-API-KEY: YOUR_API_KEY' \
--header 'Content-Type: application/json' \
--data '{
"chain": "base-sepolia",
"proof": "YOUR_SIGNATURE"
}'
```
## Related Resources
* [Onramp to Non-Crossmint Wallets](/onramp/guides/onramp-to-external-wallets) — full guide for non-Crossmint wallet onramp integration
* [React Quickstart (External Wallets tab)](/onramp/quickstarts/react) — quickstart with external wallet support
# UI Customization
Source: https://docs.crossmint.com/onramp/guides/ui-customization
Customize the onramp checkout UI to match your brand identity and design system
The onramp embedded checkout component supports extensive customization through the `appearance` prop, allowing you to match the look and feel of your application.
## Quick Start
```jsx theme={null}
```
## Customization Options
### Background
The embedded checkout component has a **transparent background** by default. The `backgroundPrimary` color only affects internal components (inputs, cards, etc.) within the iframe, not the iframe body itself.
To customize the overall background, wrap the component:
```jsx theme={null}
```
### Custom Fonts
Load custom fonts using the `fonts` array:
```jsx theme={null}
appearance={{
fonts: [
{ cssSrc: "https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap" }
]
}}
```
### Global Variables
Control the overall look and feel:
```jsx theme={null}
appearance={{
variables: {
fontFamily: "Inter, system-ui, sans-serif",
fontSizeUnit: "16px",
spacingUnit: "1rem",
borderRadius: "8px",
colors: {
borderPrimary: "#E0E0E0",
backgroundPrimary: "#FFFFFF",
textPrimary: "#000000",
textSecondary: "#666666",
danger: "#FF0000",
warning: "#FFA500",
accent: "#0074D9"
}
}
}}
```
### Component Rules
Control the visibility of specific inputs:
```jsx theme={null}
appearance={{
rules: {
DestinationInput: {
display: "hidden"
},
ReceiptEmailInput: {
display: "hidden"
}
}
}}
```
When hiding inputs, make sure to provide the corresponding values via props:
* For hidden `DestinationInput`, provide `recipient.walletAddress`
* For hidden `ReceiptEmailInput`, provide `payment.receiptEmail`
Customize label styling:
```jsx theme={null}
appearance={{
rules: {
Label: {
font: {
family: "Inter",
size: "14px",
weight: "500",
},
colors: {
text: "#333333",
},
},
},
}}
```
Style input fields:
```jsx theme={null}
appearance={{
rules: {
Input: {
borderRadius: "8px",
font: {
family: "Inter",
size: "16px",
weight: "400",
},
colors: {
text: "#000000",
background: "#FFFFFF",
border: "#E0E0E0",
boxShadow: "none",
placeholder: "#999999",
},
hover: {
colors: {
border: "#0074D9",
},
},
focus: {
colors: {
border: "#0074D9",
boxShadow: "0 0 0 2px rgba(0,116,217,0.2)",
},
},
},
},
}}
```
Customize tab appearance:
```jsx theme={null}
appearance={{
rules: {
Tab: {
borderRadius: "4px",
font: {
family: "Inter",
size: "14px",
weight: "500",
},
colors: {
text: "#666666",
background: "transparent",
},
selected: {
colors: {
text: "#000000",
background: "#F5F5F5",
},
},
hover: {
colors: {
background: "#F0F0F0",
},
},
},
},
}}
```
Style primary buttons:
```jsx theme={null}
appearance={{
rules: {
PrimaryButton: {
borderRadius: "8px",
font: {
family: "Inter",
size: "16px",
weight: "600"
},
colors: {
text: "#FFFFFF",
background: "#0074D9"
},
hover: {
colors: {
background: "#0063B8"
}
},
disabled: {
colors: {
text: "#FFFFFF",
background: "#CCCCCC"
}
}
}
}
}}
```
## Common Examples
### Modern Dark Theme
```jsx theme={null}
appearance={{
variables: {
fontFamily: "Inter, system-ui, sans-serif",
colors: {
backgroundPrimary: "#1A1A1A",
textPrimary: "#FFFFFF",
textSecondary: "#A0A0A0",
borderPrimary: "#333333",
accent: "#7928CA"
}
},
rules: {
Input: {
colors: {
background: "#2D2D2D",
border: "#404040",
text: "#FFFFFF",
placeholder: "#666666"
}
},
PrimaryButton: {
colors: {
background: "#7928CA",
text: "#FFFFFF"
},
hover: {
colors: {
background: "#6B24B2"
}
}
}
}
}}
```
### Minimal Light Theme
```jsx theme={null}
appearance={{
variables: {
fontFamily: "system-ui, sans-serif",
borderRadius: "4px",
colors: {
backgroundPrimary: "#FFFFFF",
textPrimary: "#000000",
borderPrimary: "#E0E0E0",
accent: "#000000"
}
},
rules: {
Input: {
borderRadius: "4px",
colors: {
background: "#F5F5F5",
border: "transparent"
},
focus: {
colors: {
border: "#000000",
boxShadow: "none"
}
}
},
PrimaryButton: {
borderRadius: "4px",
colors: {
background: "#000000",
text: "#FFFFFF"
}
}
}
}}
```
## Related Resources
* [React Quickstart](/onramp/quickstarts/react)
* [React Native Quickstart](/onramp/quickstarts/react-native)
* [User Onboarding](/onramp/introduction/user-onboarding)
# Restricted Countries
Source: https://docs.crossmint.com/onramp/introduction/restricted-countries
Regions where the onramp is not available
The following countries are **not supported** for onramp transactions:
| Region | Restricted Countries |
| :----------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Africa** | Burundi, Central African Republic, Democratic Republic of the Congo, Djibouti, Guinea, Guinea-Bissau, Libya, Mali, Niger, Somalia, South Sudan, Sudan, Tunisia, Zimbabwe |
| **Asia** | Afghanistan, Iran, Iraq, Lebanon, Myanmar, North Korea, Palestine, Syria, Yemen |
| **Europe** | Belarus, Bosnia and Herzegovina, Moldova, Montenegro, Russia, Serbia, Ukraine |
| **Americas** | Bolivia, Cuba, Guatemala, Haiti, Nicaragua, Venezuela |
This list may be updated based on regulatory changes.
## How Restrictions Work
* **IP-based blocking** — users connecting from restricted regions will not be able to initiate onramp transactions.
* **KYC-based blocking** — users whose identity documents indicate residence in a restricted country will be blocked during the KYC verification process.
* **Sanctions screening** — users found on international sanctions lists, including OFAC, EU, and UN lists, will be blocked from completing onramp transactions.
## Questions
If you have questions about availability in a specific country or region, contact Crossmint.
# User Onboarding and KYC
Source: https://docs.crossmint.com/onramp/introduction/user-onboarding
Register users for the Onramp
Every end user must complete KYC before buying stablecoins through the Onramp. This page explains the two decisions you need to make: who collects KYC data, and which KYC tier applies.
## Who Collects KYC Data?
This is independent of the integration mode you choose (embedded component or headless API). In either case, you decide whether Crossmint collects KYC directly from the user, or you supply data you have already collected.
| Approach | Best For | How It Works |
| :---------------------- | :----------------------------------------------------------------------------------------- | :--------------------------------------------------------------------------------------------------------------------- |
| **Crossmint-Hosted** | Teams that want the fastest integration | The user completes KYC inside Crossmint's secure embedded components. You do not need to handle any KYC data yourself. |
| **You supply KYC data** | Platforms that already verify users through their own identity verification (IDV) provider | You pass verified user data to Crossmint via API before creating an order. |
## KYC Tiers
Crossmint applies three KYC tiers based on cumulative transaction volume, transaction frequency, and risk profile:
| Tier | Applies When | Data Required |
| :------------------------------- | :----------------------------------------------------------------------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Progressive KYC** | Up to \$1,000 in total volume in the last 12 months | Name, date of birth, nationality, country, address, email, and phone number (phone required for US residents). An ID number or SSN is required for US (SSN) and EU/EEA residents; optional elsewhere. |
| **Full KYC** | Over \$1,000 in total volume in the last 12 months, or a high number of transactions | Progressive KYC data plus `dueDiligence`, `verificationHistory`, identity document, and selfie. See the [data requirements](/identity/data-requirements) page. |
| **Enhanced Due Diligence (EDD)** | High-risk individuals or regions | Full KYC data plus proof of address and proof of income |
### How It Works
Crossmint handles all KYC data collection and tier transitions. You create the order via API and render the embedded component in your app.
Create an order via API and render the Crossmint embedded checkout component in your app. The [onramp quickstart](/onramp/quickstarts/react) walks through both steps.
The embedded checkout component prompts the user for the required identity data. You do not collect or handle any KYC data yourself.
When a user crosses the \$1,000 threshold or reaches a high number of transactions, the component automatically prompts them for the additional Full KYC data. If Enhanced Due Diligence (EDD) is required, the component handles that as well. You do not need to build or manage any tier logic.
You verify users through your own identity verification (IDV) provider and pass the data to Crossmint via API. You are responsible for supplying the correct tier of data.
Before submitting any user data, you must record the user's acceptance of Crossmint's Terms of Service and Privacy Policy. The [Identity quickstart](/identity/quickstart) walks through this step.
Register the user's basic identity data via the [Create User API](/api-reference/users/upsert-user).
Call the [Trigger Identity Verification API](/api-reference/users/trigger-identity-verification) to run Crossmint's compliance checks. Verification runs asynchronously.
Poll the [Get Identity Verification Status API](/api-reference/users/get-identity-verification) until the `onramp` eligibility reads `verified`. If the status is `requires-data`, the response includes `missingData` and `missingDocuments` arrays that tell you exactly what additional information is needed. Supply it via the [Create User API](/api-reference/users/upsert-user) and/or [Upload Document API](/api-reference/users/upload-document), then re-trigger verification.
Create an order via API and render the Crossmint embedded checkout component in your app. The [onramp quickstart](/onramp/quickstarts/react) walks through both steps.
On order creation, if the user has exceeded the progressive KYC threshold and full KYC data has not been provided, the order returns an error: `"Insufficient KYC data provided for this user to perform onramp"`. Supply the required data via the [Upsert User API](/api-reference/users/upsert-user) and/or [Upload Document API](/api-reference/users/upload-document), then retry order creation. See the [data requirements](/identity/data-requirements) page for the full list of fields.
Tier transitions are not automatic. You must detect when additional data is needed and provide it. The verification status response tells you exactly what is missing.
## Onboarding for AI Agents
For AI agent use cases where a human operator has already completed KYC through your platform, you can pre-register the agent operator's identity data via the API. The agent can then execute onramp transactions without being prompted for KYC in the checkout flow.
See the [Agent Wallets Onramp](/solutions/ai-agents/agent-wallets/onramp) guide for agent-specific integration details.
## Guides
# Overview
Source: https://docs.crossmint.com/onramp/overview
Allow your users to buy stablecoins with any payment method
Crossmint Onramp lets your customers buy stablecoins directly inside your app through a white-labeled, native experience.
With plug-and-play SDKs, Crossmint handles payment processing, compliance, and chargebacks, so your team can focus on building great products.
The product is available in 160+ countries, with lightweight KYC up to \$1,000. Supported payments include debit and credit cards, Apple Pay, and Google Pay, with bank transfers coming soon.
**Need stablecoin payouts only?** If you already handle payment processing and only need stablecoin payouts or licensing support, see the [Payouts guide](/stablecoin-orchestration/regulated-transfers/overview). For treasury onramps, check the [treasury funding guide](/stablecoin-orchestration/guides/on-off-ramps/funding-company-wallets).
## Key Features
Seamless in-app experience
Faster onboarding, higher conversion
One-click stablecoin onramps
Global coverage out of the box
Crossmint assumes liability
Import user data (optional)
## Get Started
Select your framework to begin integrating. Testing in staging is self-serve. Contact sales for production.
## Learn More
* [User Onboarding](/onramp/introduction/user-onboarding)
* [Data Requirements](/identity/data-requirements)
* [Restricted Countries](/onramp/introduction/restricted-countries)
## Advanced Guides
Import user KYC data
Onramp to any wallet
Match your brand
Enable Apple Pay & Google Pay
Verify external wallets
## FAQs
Staging is fully self-serve — get an API key on the staging console and follow the docs.
Production access requires a signed Order Form and completed KYB verification. Contact sales to get started. See the [account verification documentation](/introduction/platform/account-verification) for details.
You can monitor purchases onchain or using webhooks. Sales dashboards, logs, and advanced analytics are also available in the console.
The standard transaction limits are **\$1,000 per user and day**. If you need higher limits, contact the sales team.
Crossmint Onramp is available in 160+ countries. Contact sales for the latest country list and coverage details.
# Flutter
Source: https://docs.crossmint.com/onramp/quickstarts/flutter
Allow your mobile users to buy stablecoins with cards
See the Flutter SDK checkout playground.
Crossmint Checkout lets you build a seamless onramp for Flutter users to buy stablecoins with a credit card, Apple Pay, or Google Pay.
In this guide, you will learn how to:
* Create a Crossmint order via an API endpoint
* Use Crossmint's embedded checkout widget to handle KYC, payment, and delivery automatically
## Requirements
* Flutter 3.x+
* Dart SDK ^3.11.4
* iOS 13.0+ / Android 5.0+
## 1. Prerequisites
You also need the `http` package for making API requests to your backend:
```bash theme={null}
flutter pub add http
```
Create a [server-side API key](/introduction/platform/api-keys/server-side) with the
`orders.create` and `orders.read` scopes enabled.
Create a [client-side API key](/introduction/platform/api-keys/client-side) for the
embedded checkout component.
Pass your API key and backend URL via Dart defines when running the app:
```bash theme={null}
flutter run \
--dart-define=CROSSMINT_CLIENT_API_KEY=YOUR_CLIENT_API_KEY \
--dart-define=API_URL=http://10.0.2.2:3000
```
`localhost` on a mobile device or emulator does not resolve to your development machine.
Use `10.0.2.2` for Android emulators, `127.0.0.1` for iOS simulators, or your machine's
LAN IP / an ngrok tunnel for physical devices.
## 2. Create the API Endpoint
If you want to enable onramp orders to external wallets (EOAs or third-party smart wallets) instead of Crossmint wallets, see the [Onramp to External Wallets](/onramp/guides/onramp-to-external-wallets) guide before proceeding.
To initiate an onramp, you must first create a purchase order on your backend. This example uses Express, but you can use any backend framework:
```typescript server/createOrder.ts theme={null}
import express from "express";
const app = express();
app.use(express.json());
const USDC_TOKEN_LOCATORS = {
solanaDevnet: "solana:4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU",
baseSepolia: "base-sepolia:0x036CbD53842c5426634e7929541eC2318f3dCF7e",
stellarTestnet: "stellar:CBIELTK6YBZJU5UP2WWQEUCYKLPU6AUNZ2BQ4WWFEIE3USCIHMXQDAMA",
};
app.post("/api/create-order", async (req, res) => {
const { walletAddress, receiptEmail, amount, chain } = req.body;
const serverApiKey = process.env.CROSSMINT_SERVER_SIDE_API_KEY;
if (serverApiKey == null) {
return res.status(500).json({ error: "Server API key not configured" });
}
// Use `https://www.crossmint.com` in production.
const baseUrl = process.env.CROSSMINT_BASE_URL ?? "https://staging.crossmint.com";
const response = await fetch(`${baseUrl}/api/2022-06-09/orders`, {
method: "POST",
headers: {
"Content-Type": "application/json",
"x-api-key": serverApiKey,
},
body: JSON.stringify({
lineItems: [
{
tokenLocator: USDC_TOKEN_LOCATORS[chain],
executionParameters: {
mode: "exact-in",
amount,
},
},
],
payment: {
method: "card",
receiptEmail,
},
recipient: {
walletAddress,
},
}),
});
if (!response.ok) {
const error = await response.json();
return res.status(response.status).json(error);
}
const data = await response.json();
res.json(data);
});
app.listen(3000);
```
## 3. Build an Onramp Checkout UI in Your App
Create a Flutter screen that handles order creation and displays a checkout:
```dart lib/screens/onramp_screen.dart theme={null}
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'package:crossmint_flutter/crossmint_flutter_ui.dart';
const clientApiKey = String.fromEnvironment('CROSSMINT_CLIENT_API_KEY');
// Replace with your backend URL.
// On Android emulator use 10.0.2.2 instead of localhost.
// On a physical device use your machine's LAN IP or an ngrok tunnel.
const apiUrl = String.fromEnvironment('API_URL', defaultValue: 'http://10.0.2.2:3000');
class OnrampScreen extends StatefulWidget {
const OnrampScreen({super.key});
@override
State createState() => _OnrampScreenState();
}
class _OnrampScreenState extends State {
final _emailController = TextEditingController(text: 'user@example.com');
final _walletController = TextEditingController();
final _amountController = TextEditingController(text: '5');
String _chain = 'solanaDevnet';
bool _isLoading = false;
Map? _order;
Future _createOrder() async {
setState(() => _isLoading = true);
try {
final response = await http.post(
Uri.parse('$apiUrl/api/create-order'),
headers: {'Content-Type': 'application/json'},
body: jsonEncode({
'walletAddress': _walletController.text,
'receiptEmail': _emailController.text,
'amount': _amountController.text,
'chain': _chain,
}),
);
if (response.statusCode != 200) {
throw Exception('Failed to create order');
}
final data = jsonDecode(response.body);
setState(() {
_order = {
'orderId': data['order']['orderId'],
'clientSecret': data['clientSecret'],
};
});
} catch (e) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Failed to create order: $e')),
);
} finally {
setState(() => _isLoading = false);
}
}
@override
Widget build(BuildContext context) {
if (_order != null) {
return Scaffold(
body: CrossmintEmbeddedCheckout(
apiKey: clientApiKey,
config: CrossmintCheckoutConfig(
order: CrossmintCheckoutExistingOrder(
orderId: _order!['orderId']!,
clientSecret: _order!['clientSecret'],
),
payment: CrossmintCheckoutPayment(
fiat: CrossmintCheckoutFiatPayment(enabled: true),
crypto: CrossmintCheckoutCryptoPayment(enabled: false),
),
),
onOrderUpdated: (order) => debugPrint('Order: $order'),
),
);
}
return Scaffold(
body: Padding(
padding: const EdgeInsets.all(24),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text('Buy USDC',
style: TextStyle(fontSize: 24, fontWeight: FontWeight.w600)),
const SizedBox(height: 24),
const Text('Network', style: TextStyle(fontWeight: FontWeight.w500)),
const SizedBox(height: 8),
SegmentedButton(
segments: const [
ButtonSegment(value: 'solanaDevnet', label: Text('Solana')),
ButtonSegment(value: 'baseSepolia', label: Text('Base')),
ButtonSegment(value: 'stellarTestnet', label: Text('Stellar')),
],
selected: {_chain},
onSelectionChanged: (v) => setState(() => _chain = v.first),
),
const SizedBox(height: 20),
TextField(
controller: _emailController,
decoration: const InputDecoration(
labelText: 'Email',
border: OutlineInputBorder(),
),
),
const SizedBox(height: 12),
TextField(
controller: _walletController,
decoration: const InputDecoration(
labelText: 'Recipient Wallet',
border: OutlineInputBorder(),
),
),
const SizedBox(height: 12),
TextField(
controller: _amountController,
decoration: const InputDecoration(
labelText: 'Amount (USD)',
border: OutlineInputBorder(),
),
keyboardType: TextInputType.number,
),
const SizedBox(height: 16),
SizedBox(
width: double.infinity,
child: ElevatedButton(
onPressed: _isLoading ? null : _createOrder,
style: ElevatedButton.styleFrom(
backgroundColor: Colors.black,
padding: const EdgeInsets.all(16),
),
child: _isLoading
? const CircularProgressIndicator(color: Colors.white)
: const Text('Continue to Checkout',
style: TextStyle(color: Colors.white, fontSize: 16)),
),
),
],
),
),
);
}
@override
void dispose() {
_emailController.dispose();
_walletController.dispose();
_amountController.dispose();
super.dispose();
}
}
```
## 4. Add the Screen to Your App
Import and render the `OnrampScreen` in your app:
```dart lib/main.dart theme={null}
import 'package:flutter/material.dart';
import 'screens/onramp_screen.dart';
void main() {
runApp(const MaterialApp(home: OnrampScreen()));
}
```
The integration is complete. When users fill in their details and tap "Continue to Checkout", the API creates an order and the embedded checkout widget handles KYC, payment, and delivery automatically.
**Testing:** Use the test credit card number `4242 4242 4242 4242` with any future expiration date and any 3-digit CVC.
## 5. Transaction Completion
## 6. Next Steps
# Kotlin
Source: https://docs.crossmint.com/onramp/quickstarts/kotlin
Allow your Android users to buy stablecoins with credit cards and Google Pay
In this guide, you'll learn how to:
* Install the Kotlin SDK
* Create an Onramp order for your authenticated users
* Use the embedded checkout component to handle KYC, payment, and delivery
View the Kotlin checkout example repository.
## Requirements
* **Android SDK 24+**
* **Jetpack Compose**
* Android Studio Arctic Fox or later
## 1. Installation
Install the Crossmint Kotlin SDK using Maven Central.
Add the following dependency to your app's `build.gradle.kts` file:
```kotlin theme={null}
dependencies {
implementation("com.crossmint:crossmint-sdk-android:0.0.9")
}
```
Create a [server-side API key](https://docs.crossmint.com/introduction/platform/api-keys/server-side) with the
`orders.create` and `orders.read` scopes enabled. In staging, all scopes are included in your API key.
## 2. Create Order
If you want to enable onramp orders to external wallets (EOAs or third-party smart wallets) instead of Crossmint wallets, see the [Onramp to External Wallets](/onramp/guides/onramp-to-external-wallets) guide before proceeding.
Use the [Create Order API](/api-reference/headless/create-order) to initiate the purchase process from your backend.
## 3. Use Component
Once you have the order response with `orderId` and `clientSecret`, use Crossmint's embedded checkout component in your Jetpack Compose view.
The component supports **native Google Pay** for a seamless Android payment experience.
```kotlin theme={null}
import com.crossmint.kotlin.checkout.CheckoutEnvironment
import com.crossmint.kotlin.checkout.CrossmintEmbeddedCheckout
import com.crossmint.kotlin.checkout.models.*
CrossmintEmbeddedCheckout(
orderId = "",
clientSecret = "",
payment = CheckoutPayment(
crypto = CheckoutCryptoPayment(enabled = false),
fiat = CheckoutFiatPayment(
enabled = true,
allowedMethods = CheckoutAllowedMethods(
googlePay = true, // Native Google Pay support
applePay = false,
card = true
)
)
),
appearance = CheckoutAppearance(
rules = CheckoutAppearanceRules(
destinationInput = CheckoutDestinationInputRule(display = "hidden"),
receiptEmailInput = CheckoutReceiptEmailInputRule(display = "hidden")
)
),
environment = CheckoutEnvironment.STAGING
)
```
### Key Features
* **Native Google Pay**: Seamless integration with Android payment system
* **Jetpack Compose Components**: Modern declarative UI framework
* **Automatic KYC**: Handles verification flow automatically
* **Customizable Appearance**: Control visibility of UI elements
## 4. Track Order
## 5. Transaction Completion
## 6. Next Steps
* View the Kotlin checkout example repo on GitHub
# React
Source: https://docs.crossmint.com/onramp/quickstarts/react
Allow your users to buy stablecoins with cards
In this guide, you will learn how to:
* Install the React SDK
* Create an Onramp order for your authenticated users
* Use the embedded checkout component to handle KYC, payment, and delivery
See a full working example.
You can test the Onramp self-serve in staging. To enable it in production, contact sales.
## 1. Prerequisites
Install the Crossmint client SDK:
Create a [server-side API key](https://docs.crossmint.com/introduction/platform/api-keys/server-side) with the
`orders.create` and `orders.read` scopes enabled.
Create a [client-side API key](https://docs.crossmint.com/introduction/platform/api-keys/client-side) for the
embedded checkout component.
Add environment variables to your `.env.local`:
```sh .env.local theme={null}
NEXT_PUBLIC_CROSSMINT_CLIENT_SIDE_API_KEY="_YOUR_CLIENT_API_KEY_"
CROSSMINT_SERVER_SIDE_API_KEY="_YOUR_SERVER_API_KEY_"
```
## 2. Create the Server Action
Create a server action that will create orders via the Crossmint API:
```typescript app/actions/createOrder.ts theme={null}
"use server";
const USDC_TOKEN_LOCATORS = {
solanaDevnet: "solana:4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU",
baseSepolia: "base-sepolia:0x036CbD53842c5426634e7929541eC2318f3dCF7e",
stellarTestnet: "stellar:CBIELTK6YBZJU5UP2WWQEUCYKLPU6AUNZ2BQ4WWFEIE3USCIHMXQDAMA",
};
interface CreateOrderParams {
walletAddress: string;
receiptEmail: string;
amount: string;
chain: "solanaDevnet" | "baseSepolia" | "stellarTestnet";
}
export async function createOrder({ walletAddress, receiptEmail, amount, chain }: CreateOrderParams) {
const serverApiKey = process.env.CROSSMINT_SERVER_SIDE_API_KEY;
if (serverApiKey == null) {
throw new Error("CROSSMINT_SERVER_SIDE_API_KEY is not set");
}
const response = await fetch("https://staging.crossmint.com/api/2022-06-09/orders", {
method: "POST",
headers: {
"Content-Type": "application/json",
"x-api-key": serverApiKey,
},
body: JSON.stringify({
lineItems: [
{
tokenLocator: USDC_TOKEN_LOCATORS[chain],
executionParameters: {
mode: "exact-in",
amount,
},
},
],
payment: {
method: "card",
receiptEmail,
},
recipient: {
walletAddress,
},
}),
});
if (!response.ok) {
const error = await response.json();
throw new Error(error.message || "Failed to create order");
}
return response.json();
}
```
## 3. Build the Onramp Component
Create a client component that handles the order creation and displays the embedded checkout:
```tsx app/components/OnrampCheckout.tsx theme={null}
"use client";
import { useState } from "react";
import { CrossmintProvider, CrossmintEmbeddedCheckout } from "@crossmint/client-sdk-react-ui";
import { createOrder } from "../actions/createOrder";
const CLIENT_API_KEY = process.env.NEXT_PUBLIC_CROSSMINT_CLIENT_SIDE_API_KEY || "";
// Destination wallets for testing transfers on each network
const TEST_WALLETS = {
solanaDevnet: "",
baseSepolia: "",
stellarTestnet: "",
};
export default function OnrampCheckout() {
const [order, setOrder] = useState<{ orderId: string; clientSecret: string } | null>(null);
const [chain, setChain] = useState<"solanaDevnet" | "baseSepolia" | "stellarTestnet">("solanaDevnet");
const [email, setEmail] = useState("user@example.com");
const [wallet, setWallet] = useState(TEST_WALLETS.solanaDevnet);
const [amount, setAmount] = useState("5");
const [isLoading, setIsLoading] = useState(false);
function handleChainChange(newChain: "solanaDevnet" | "baseSepolia" | "stellarTestnet") {
setChain(newChain);
setWallet(TEST_WALLETS[newChain]);
}
async function handleSubmit() {
setIsLoading(true);
try {
const result = await createOrder({ walletAddress: wallet, receiptEmail: email, amount, chain });
setOrder({ orderId: result.order.orderId, clientSecret: result.clientSecret });
} catch (error) {
console.error("Failed to create order:", error);
} finally {
setIsLoading(false);
}
}
if (order != null) {
return (
);
}
```
## 4. Add the Component to Your Page
Import and render the `OnrampCheckout` component in your page:
```tsx app/page.tsx theme={null}
import OnrampCheckout from "./components/OnrampCheckout";
export default function Home() {
return (
);
}
```
That's it! When users fill in their details and click "Continue to Checkout", the server action creates an order and the embedded checkout component handles KYC, payment, and delivery automatically.
Use this tab if you want to onramp to external wallets — EOAs or smart wallets not created by Crossmint.
External wallet onramp requires two additional steps compared to Crossmint wallets: linking the wallet to a Crossmint user, and handling ownership verification when transaction thresholds are exceeded.
## 2. Link External Wallet
Before creating an onramp order to an external wallet, you must link the wallet to a Crossmint user using the Link External Wallet API.
Create a server action to handle the wallet linking:
```typescript app/actions/linkWallet.ts theme={null}
"use server";
interface LinkWalletParams {
userEmail: string;
walletAddress: string;
chain: string;
proof?: string;
}
export async function linkWallet({ userEmail, walletAddress, chain, proof }: LinkWalletParams) {
const serverApiKey = process.env.CROSSMINT_SERVER_SIDE_API_KEY;
if (serverApiKey == null) {
throw new Error("CROSSMINT_SERVER_SIDE_API_KEY is not set");
}
const userLocator = `email:${userEmail}`;
const body: Record = { chain };
if (proof) {
body.proof = proof;
}
const response = await fetch(
`https://staging.crossmint.com/api/2025-06-09/users/${userLocator}/linked-wallets/${walletAddress}`,
{
method: "PUT",
headers: {
"Content-Type": "application/json",
"x-api-key": serverApiKey,
},
body: JSON.stringify(body),
}
);
if (!response.ok) {
const error = await response.json();
throw new Error(error.message || "Failed to link wallet");
}
return response.json();
}
```
The `proof` field is optional:
* If you omit it, the wallet is linked without proving ownership yet. The API responds with a `verificationChallenge` (following the CAIP-122 standard) that you can use later to verify ownership.
* If you pass it, you prove ownership of the wallet in the same request.
## 3. Create the Server Action
Create a server action that will create orders via the Crossmint API:
```typescript app/actions/createOrder.ts theme={null}
"use server";
const USDC_TOKEN_LOCATORS = {
solanaDevnet: "solana:4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU",
baseSepolia: "base-sepolia:0x036CbD53842c5426634e7929541eC2318f3dCF7e",
stellarTestnet: "stellar:CBIELTK6YBZJU5UP2WWQEUCYKLPU6AUNZ2BQ4WWFEIE3USCIHMXQDAMA",
};
interface CreateOrderParams {
walletAddress: string;
receiptEmail: string;
amount: string;
chain: "solanaDevnet" | "baseSepolia" | "stellarTestnet";
}
export async function createOrder({ walletAddress, receiptEmail, amount, chain }: CreateOrderParams) {
const serverApiKey = process.env.CROSSMINT_SERVER_SIDE_API_KEY;
if (serverApiKey == null) {
throw new Error("CROSSMINT_SERVER_SIDE_API_KEY is not set");
}
const response = await fetch("https://staging.crossmint.com/api/2022-06-09/orders", {
method: "POST",
headers: {
"Content-Type": "application/json",
"x-api-key": serverApiKey,
},
body: JSON.stringify({
lineItems: [
{
tokenLocator: USDC_TOKEN_LOCATORS[chain],
executionParameters: {
mode: "exact-in",
amount,
},
},
],
payment: {
method: "card",
receiptEmail,
},
recipient: {
walletAddress,
},
}),
});
if (!response.ok) {
const error = await response.json();
throw new Error(error.message || "Failed to create order");
}
return response.json();
}
```
### Ownership Verification
Depending on the transaction value and the user's past activity, the order may or may not require wallet ownership verification:
* **Below threshold**: If the transaction value is below **$1,000** **and** the user's past 30-day onramp transaction volume is below **$1,000**, ownership verification is not required. The order proceeds directly to the KYC/payment phase.
* **Above threshold**: If either threshold is surpassed, the order status will be `requires-recipient-verification` and the user must sign a message to prove wallet ownership.
When ownership verification is required, the order response includes a `preparation.message` field that the user must sign:
```json theme={null}
{
"payment": {
"status": "requires-recipient-verification",
"preparation": {
"message": "crossmint.com wants you to sign in with your blockchain account:\n0x1234...5678\n\n..."
}
}
}
```
## 4. Handle Wallet Verification
If the order requires ownership verification, the user must sign the `preparation.message` returned in the order response using their wallet's private key.
```js EVM (ethers.js) theme={null}
import { Wallet } from "ethers";
const wallet = new Wallet("YOUR_PRIVATE_KEY");
const message = order.payment.preparation.message;
const signature = await wallet.signMessage(message);
```
```js Solana (tweetnacl) theme={null}
import nacl from "tweetnacl";
import { decodeUTF8 } from "tweetnacl-util";
import { encode as base64Encode } from "@stablelib/base64";
const message = order.payment.preparation.message;
const messageBytes = decodeUTF8(message);
const signatureBytes = nacl.sign.detached(messageBytes, keypair.secretKey);
const signature = base64Encode(signatureBytes);
```
Once signed, submit the signature using the same Link External Wallet API by calling the `linkWallet` server action with the `proof` parameter set to the signature. Once ownership is verified, the order proceeds to the next phase (`requires-kyc` or `awaiting-payment`).
## 5. Build the Onramp Component
Create a client component that handles wallet linking, order creation, and displays the embedded checkout:
```tsx app/components/OnrampCheckout.tsx theme={null}
"use client";
import { useState } from "react";
import { CrossmintProvider, CrossmintEmbeddedCheckout } from "@crossmint/client-sdk-react-ui";
import { createOrder } from "../actions/createOrder";
import { linkWallet } from "../actions/linkWallet";
const CLIENT_API_KEY = process.env.NEXT_PUBLIC_CROSSMINT_CLIENT_SIDE_API_KEY || "";
// Destination wallets for testing transfers on each network
const TEST_WALLETS = {
solanaDevnet: "YOUR_DESTINATION_WALLET_ADDRESS",
baseSepolia: "YOUR_DESTINATION_WALLET_ADDRESS",
stellarTestnet: "YOUR_DESTINATION_WALLET_ADDRESS",
};
const CHAIN_MAP = {
solanaDevnet: "solana",
baseSepolia: "base-sepolia",
stellarTestnet: "stellar",
};
export default function OnrampCheckout() {
const [order, setOrder] = useState<{ orderId: string; clientSecret: string } | null>(null);
const [chain, setChain] = useState<"solanaDevnet" | "baseSepolia" | "stellarTestnet">("solanaDevnet");
const [email, setEmail] = useState("user@example.com");
const [wallet, setWallet] = useState(TEST_WALLETS.solanaDevnet);
const [amount, setAmount] = useState("5");
const [isLoading, setIsLoading] = useState(false);
function handleChainChange(newChain: "solanaDevnet" | "baseSepolia" | "stellarTestnet") {
setChain(newChain);
setWallet(TEST_WALLETS[newChain]);
}
async function handleSubmit() {
setIsLoading(true);
try {
// Step 1: Link the external wallet to the Crossmint user
await linkWallet({
userEmail: email,
walletAddress: wallet,
chain: CHAIN_MAP[chain],
});
// Step 2: Create the onramp order
const result = await createOrder({ walletAddress: wallet, receiptEmail: email, amount, chain });
setOrder({ orderId: result.order.orderId, clientSecret: result.clientSecret });
} catch (error) {
console.error("Failed to create order:", error);
} finally {
setIsLoading(false);
}
}
if (order != null) {
return (
);
}
```
## 6. Add the Component to Your Page
Import and render the `OnrampCheckout` component in your page:
```tsx app/page.tsx theme={null}
import OnrampCheckout from "./components/OnrampCheckout";
export default function Home() {
return (
);
}
```
That is it! When users fill in their details and click "Continue to Checkout", the app links the external wallet, creates an order, and the embedded checkout component handles KYC, payment, and delivery automatically.
**Testing:** Use the test credit card number `4242 4242 4242 4242` with any future expiration date and any 3-digit CVC.
## Transaction Completion
## Next Steps
# React Native
Source: https://docs.crossmint.com/onramp/quickstarts/react-native
Allow your mobile users to buy stablecoins with cards
In this guide, you will learn how to:
* Install the React Native SDK
* Create an Onramp order for your authenticated users
* Use the embedded checkout component to handle KYC, payment, and delivery
See a full working example.
You can test the Onramp self-serve in staging. To enable it in production, contact sales.
## Requirements
* React Native 0.60+
* Expo (recommended) or bare React Native CLI
* iOS 13.0+ / Android 5.0+
## 1. Prerequisites
Create a [server-side API key](https://docs.crossmint.com/introduction/platform/api-keys/server-side) with the
`orders.create` and `orders.read` scopes enabled.
Create a [client-side API key](https://docs.crossmint.com/introduction/platform/api-keys/client-side) for the
embedded checkout component.
Add environment variables to your `.env`:
```sh .env theme={null}
EXPO_PUBLIC_CROSSMINT_CLIENT_SIDE_API_KEY="_YOUR_CLIENT_API_KEY_"
```
For Expo projects, use the `EXPO_PUBLIC_` prefix. For bare React Native, configure environment variables according to your setup.
## 2. Create the API Endpoint
If you want to enable onramp orders to external wallets (EOAs or third-party smart wallets) instead of Crossmint wallets, see the [Onramp to External Wallets](/onramp/guides/onramp-to-external-wallets) guide before proceeding.
To initiate an onramp, you must first create a purchase order on your backend. This example uses Express, but you can use any backend framework:
```typescript server/createOrder.ts theme={null}
import express from "express";
const app = express();
app.use(express.json());
const USDC_TOKEN_LOCATORS = {
solanaDevnet: "solana:4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU",
baseSepolia: "base-sepolia:0x036CbD53842c5426634e7929541eC2318f3dCF7e",
stellarTestnet: "stellar:CBIELTK6YBZJU5UP2WWQEUCYKLPU6AUNZ2BQ4WWFEIE3USCIHMXQDAMA",
};
app.post("/api/create-order", async (req, res) => {
const { walletAddress, receiptEmail, amount, chain } = req.body;
const serverApiKey = process.env.CROSSMINT_SERVER_SIDE_API_KEY;
if (serverApiKey == null) {
return res.status(500).json({ error: "Server API key not configured" });
}
const response = await fetch("https://staging.crossmint.com/api/2022-06-09/orders", {
method: "POST",
headers: {
"Content-Type": "application/json",
"x-api-key": serverApiKey,
},
body: JSON.stringify({
lineItems: [
{
tokenLocator: USDC_TOKEN_LOCATORS[chain],
executionParameters: {
mode: "exact-in",
amount,
},
},
],
payment: {
method: "card",
receiptEmail,
},
recipient: {
walletAddress,
},
}),
});
if (!response.ok) {
const error = await response.json();
return res.status(response.status).json(error);
}
const data = await response.json();
res.json(data);
});
app.listen(3000);
```
## 3. Build an Onramp checkout UI in your app
Now let's go to your app. Create a React Native screen that handles order creation and displays a checkout:
```tsx screens/OnrampScreen.tsx theme={null}
import { useState } from "react";
import { View, Text, TextInput, TouchableOpacity, StyleSheet, ActivityIndicator } from "react-native";
import { CrossmintProvider, CrossmintEmbeddedCheckout } from "@crossmint/client-sdk-react-native-ui";
const CLIENT_API_KEY = process.env.EXPO_PUBLIC_CROSSMINT_CLIENT_SIDE_API_KEY || "";
const API_URL = "http://localhost:3000"; // Replace with your backend URL
// Destination wallets for testing transfers on each network
const TEST_WALLETS = {
solanaDevnet: "",
baseSepolia: "",
stellarTestnet: "",
};
type Chain = "solanaDevnet" | "baseSepolia" | "stellarTestnet";
export default function OnrampScreen() {
const [order, setOrder] = useState<{ orderId: string; clientSecret: string } | null>(null);
const [chain, setChain] = useState("solanaDevnet");
const [email, setEmail] = useState("user@example.com");
const [wallet, setWallet] = useState(TEST_WALLETS.solanaDevnet);
const [amount, setAmount] = useState("5");
const [isLoading, setIsLoading] = useState(false);
function handleChainChange(newChain: Chain) {
setChain(newChain);
setWallet(TEST_WALLETS[newChain]);
}
async function handleSubmit() {
setIsLoading(true);
try {
const response = await fetch(`${API_URL}/api/create-order`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ walletAddress: wallet, receiptEmail: email, amount, chain }),
});
if (!response.ok) {
throw new Error("Failed to create order");
}
const result = await response.json();
setOrder({ orderId: result.order.orderId, clientSecret: result.clientSecret });
} catch (error) {
console.error("Failed to create order:", error);
} finally {
setIsLoading(false);
}
}
if (order != null) {
return (
);
}
return (
Buy USDCNetwork handleChainChange("solanaDevnet")}
>
Solana Devnet
handleChainChange("baseSepolia")}
>
Base Sepolia
handleChainChange("stellarTestnet")}
>
Stellar Testnet
EmailRecipient WalletAmount (USD)
{isLoading ? (
) : (
Continue to Checkout
)}
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
padding: 24,
backgroundColor: "#fff",
},
checkoutContainer: {
flex: 1,
backgroundColor: "#fff",
},
title: {
fontSize: 24,
fontWeight: "600",
color: "#111",
marginBottom: 24,
},
fieldGroup: {
marginBottom: 20,
},
label: {
fontSize: 14,
fontWeight: "500",
color: "#111",
marginBottom: 8,
},
toggleRow: {
flexDirection: "row",
gap: 8,
},
toggleButton: {
flex: 1,
padding: 12,
borderRadius: 8,
borderWidth: 1,
borderColor: "#d1d5db",
backgroundColor: "#fff",
alignItems: "center",
},
toggleButtonActiveSolana: {
backgroundColor: "#16a34a",
borderColor: "#16a34a",
},
toggleButtonActiveBase: {
backgroundColor: "#2563eb",
borderColor: "#2563eb",
},
toggleButtonActiveStellar: {
backgroundColor: "#9333ea",
borderColor: "#9333ea",
},
toggleText: {
fontSize: 14,
fontWeight: "500",
color: "#111",
},
toggleTextActive: {
color: "#fff",
},
input: {
borderWidth: 1,
borderColor: "#d1d5db",
borderRadius: 8,
padding: 12,
fontSize: 14,
color: "#111",
backgroundColor: "#fff",
},
monoInput: {
fontFamily: "monospace",
},
submitButton: {
backgroundColor: "#000",
padding: 16,
borderRadius: 8,
alignItems: "center",
marginTop: 8,
},
submitButtonDisabled: {
opacity: 0.5,
},
submitButtonText: {
color: "#fff",
fontSize: 16,
fontWeight: "500",
},
});
```
## 4. Add the Screen to Your App
Import and render the `OnrampScreen` in your app:
```tsx App.tsx theme={null}
import { SafeAreaView, StyleSheet } from "react-native";
import OnrampScreen from "./screens/OnrampScreen";
export default function App() {
return (
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: "#fff",
},
});
```
That's it! When users fill in their details and tap "Continue to Checkout", the API creates an order and the embedded checkout component handles KYC, payment, and delivery automatically.
**Testing:** Use the test credit card number `4242 4242 4242 4242` with any future expiration date and any 3-digit CVC.
## 5. Transaction Completion
## 6. Next Steps
# Swift
Source: https://docs.crossmint.com/onramp/quickstarts/swift
Allow your iOS users to buy stablecoins with credit cards and Apple Pay
In this guide, you'll learn how to:
* Install the Swift SDK
* Create an Onramp order for your authenticated users
* Use the embedded checkout component to handle KYC, payment, and delivery
View the open source Swift Checkout SDK repository.
## Requirements
* **iOS 14.0+**
* **SwiftUI** framework
* Xcode 13.0 or later
## 1. Installation
Install the Crossmint Swift SDK using Swift Package Manager.
In Xcode, go to **File > Add Package Dependencies** and add:
```
https://github.com/Crossmint/crossmint-checkout-swift
```
Select the `CrossmintCheckout` product for your target.
Create a [server-side API key](https://docs.crossmint.com/introduction/platform/api-keys/server-side) with the
`orders.create` and `orders.read` scopes enabled. In staging, all scopes are included in your API key.
## 2. Create Order
If you want to enable onramp orders to external wallets (EOAs or third-party smart wallets) instead of Crossmint wallets, see the [Onramp to External Wallets](/onramp/guides/onramp-to-external-wallets) guide before proceeding.
Use the [Create Order API](/api-reference/headless/create-order) to initiate the purchase process from your backend.
## 3. Use Component
Once you have the order response with `orderId` and `clientSecret`, use Crossmint's embedded checkout component in your SwiftUI view.
The component supports **native Apple Pay** for a seamless iOS payment experience.
```swift theme={null}
import SwiftUI
import CrossmintCheckout
struct ContentView: View {
var body: some View {
CrossmintEmbeddedCheckout(
orderId: ,
clientSecret: ,
payment: CheckoutPayment(
crypto: CheckoutCryptoPayment(enabled: false),
fiat: CheckoutFiatPayment(
enabled: true,
allowedMethods: CheckoutAllowedMethods(
googlePay: false,
applePay: true, // Native Apple Pay support
card: true
)
)
),
appearance: CheckoutAppearance(
rules: CheckoutAppearanceRules(
destinationInput: CheckoutDestinationInputRule(display: "hidden"),
receiptEmailInput: CheckoutReceiptEmailInputRule(display: "hidden")
)
)
)
}
}
```
### Key Features
* **Native Apple Pay**: Smooth integration with iOS payment system
* **SwiftUI Components**: Modern declarative UI framework
* **Automatic KYC**: Handles verification flow automatically
* **Customizable Appearance**: Control visibility of UI elements
## 4. Track Order
## 5. Transaction Completion
## 6. Next Steps
* View the Swift Checkout SDK source code on GitHub
# NFT Drops Gated by Accesslists
Source: https://docs.crossmint.com/payments/advanced/accesslists
Allow purchasing in accesslist-protected NFT drops
## Access Lists
Accesslist support is a premium feature. To get started, [contact sales](https://www.crossmint.com/contact/sales)
Crossmint allows you to whitelist both wallet and email addresses. Once the team
has approved your request, you will need to provide the following information:
1. Your Crossmint `clientId` or `collectionId` for the collection.
2. The list of wallet and email addresses to be included in the accesslist.
If you whitelist users' email addresses, Crossmint will create wallets associated with those email addresses and
provide them to you for inclusion in the accesslist. Users will then be able to authenticate during the purchase and
execute the transaction.
You can set up accesslists using Merkle Trees or inserting the list of whitelisted addresses directly on the smart contract:
### A. Merkle Tree Accesslists
While using Merkle Trees is more gas-efficient, it involves a slightly more complex setup.
Here is an example accesslist mint function:
```solidity Solidity theme={null}
//SPDX-License-Identifier: Unlicense
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol";
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
contract Accesslist is ERC721, Ownable {
bytes32 public merkleRoot;
uint256 public nextTokenId;
mapping(address => bool) public claimed;
constructor() ERC721("ExampleNFT", "NFT") {
merkleRoot = 0x0;
}
function isWhiteListed(bytes32[] memory proof, bytes32 leaf) public view returns(bool) {
return MerkleProof.verify(proof, merkleRoot, leaf);
}
function setMerkleRoot(bytes32 merkleRoot_) external onlyOwner {
merkleRoot = merkleRoot_;
}
function mint(address to, bytes32[] calldata merkleProof) public payable {
require(claimed[to] == false, "already claimed");
claimed[to] = true;
require(
isWhiteListed(
merkleProof,
keccak256(abi.encodePacked(to))
),
"invalid merkle proof"
);
nextTokenId++;
_mint(to, nextTokenId);
}
}
```
### B. Mapping-Based Accesslists
The most straightforward method of whitelisting involves maintaining
a mapping of the whitelisted addresses within your smart contract.
This approach is faster but consumes more gas and savvy developers
may be able to determine the addresses of the accesslist.
```solidity Solidity theme={null}
mapping(address => uint256) public whitelistMapping;
```
```solidity Solidity theme={null}
function presale(address _to, uint256 _count) external payable {
// ensure _to address has tokens left to mint
require(whitelistMapping[_to] > 0, "no whitelist tokens for user");
// decrement mapping for user
whitelistMapping[_to] -= _count;
// presale minting logic here
}
```
You can set up accesslists on your Candy Machine using Merkle Trees or SPL Tokens
### A. Merkle Tree Accesslists
Simply send the user IDs to Crossmint. Once you have the Merkle proof, you can pass it into the Crossmint Hosted Checkout `lineItems.callData` as a string, as in the example below:
```jsx React theme={null}
```
### B. SPL Token-based Accesslists
You can whitelist wallets by airdropping them SPL tokens.
## Geofencing
Geofencing support is a premium feature only available to customers on a paid plan. To get started, [contact
sales](https://www.crossmint.com/contact/sales)
Crossmint supports geofencing capabilities through our Customer Success Engineering (CSE) team. Our CSE team can work with you to block transactions from specific countries or geographic regions via Stripe rules.
To set up geofencing for your project, please contact our sales team to discuss your specific requirements and implementation details.
# NFT Drops with Dynamic Price or Quantity
Source: https://docs.crossmint.com/payments/advanced/dynamic-quantity-and-price
Details about implementing dynamic functionality into the Checkout
See the below examples to understand how to implement dynamic pricing or quantity into your dApp.
The examples below use the [Hosted Checkout](/payments/pay-button/overview), but the same approach is compatible
with the [Embedded Checkout](/payments/embedded/overview) as well.
```jsx React theme={null}
import { useState } from 'react';
import { CrossmintProvider, CrossmintHostedCheckout } from "@crossmint/client-sdk-react-ui";
function App() {
const [mintAmount, setMintAmount] = useState(1);
const nftCost = 0.001;
const apiClientId = '_YOUR_API_CLIENT_ID_';
const collectionId = '_YOUR_COLLECTION_ID_';
const handleDecrement = () => {
if (mintAmount <= 1) return;
setMintAmount(mintAmount - 1);
}
const handleIncrement = () => {
if (mintAmount >= 3) return;
setMintAmount(mintAmount + 1);
}
return (
<>
>
);
}
export default App;
```
# Improving Conversion
Source: https://docs.crossmint.com/payments/advanced/improving-conversion
Best practices to maximize checkout conversion
Here are a selection of best practices you can follow to optimize conversion on your checkout flow.
## 1. Ensure your checkout has the right locale
Improper locales can significantly reduce conversion. Double check that your checkout is configured to work in the locales you have customers.
You can manage the locale in your [integrated checkout components](/sdk-reference/checkout/react/components), or your [payment order objects.](/payments/headless/guides/order-lifecycle/quote-phase#update-the-locale)
## 2. Choose the best payment methods
You may have data that shows your customers convert better with one payment method over another one. Customizing your checkout to feature these preferred payment methods by default is a good way to improve your conversion.
**Embedded Checkout** - Set the default payment method as your customers most preferred one (i.e. Apple Pay, Sol, etc.)
**Headless Checkout** - you have full control over your UI. Put the payment method your customers prefer as default option, and pre-fill as much information as you can.
## 3. Further customize your checkout flow
You may know your customer very well, and what exactly increases conversion for them: large images, progress bars, loyalty point reward offers.
Building these bespoke checkout experiences can increase your conversion. [Embedded](/payments/embedded/overview) or [headless checkout](/payments/headless/overview) are more customizable checkout tools.
# NFT Marketplaces & Launchpads
Source: https://docs.crossmint.com/payments/advanced/marketplaces-and-launchpads
Add fiat and cross-chain crypto payments to your platform
Crossmint offers additional services to marketplaces and launchpads,
including:
* Custom branding and email receipts
* Dedicated Slack channel with Crossmint's support team
* Custom SLAs
* APIs to register new collections programmatically
* Increased purchase limits
* Cart mode and ability to sweep the floor
...and more
### Integrating Crossmint into a marketplace
Integrating Crossmint into a marketplace can be as simple as
adding five lines of code, depending on the level of customization required.
The following snippet would be sufficient to render a button that enables
the purchase of one of your listings with credit card.
```jsx EVM theme={null}
```
```jsx Solana theme={null}
```
[Contact sales](https://www.crossmint.com/contact/sales) to get started.If you are building a new marketplace, use one of the supported contracts for a faster integration. If you are looking for help building a marketplace, [contact us](https://www.crossmint.com/contact/sales) and the Crossmint team will introduce you to the right partner.
### FAQs
Crossmint supports a wide range of contracts across chains, including:
| Blockchain | Contract |
| :--------- | :------------------------------------------------------------------------------------------ |
| EVM | Rarible, Seaport, OpenSea, Zora, LooksRare, 0x, Foundation, CoinbaseNFT, Sudoswap, Manifold |
| Solana | Auction House, Magic Eden API, Hyperspace, Exchange.art |
Custom integrations available only for enterprise clients.
You can see a live implementation of the payment button on [Tensor](https://tensor.trade/).
The default transaction limit is 7,000 USD. If you need a higher limit, [contact us](https://www.crossmint.com/contact/sales)
### Integrating Crossmint into a launchpad
The integration is identical to that of the standard digital asset checkout. However, Crossmint offers additional services to improve the creator experience, including the ability to register collections via API.
[Contact sales](https://www.crossmint.com/contact/sales) to get started.
### FAQs
Crossmint powers most major launchpads across chains. You can see in action on [Manifold](https://app.manifold.xyz/c/sleeping-potato) and [Highlight](https://highlight.xyz/mint/6525a4b606bc6fa5b8acdcfa).
For primary sales, Crossmint requires creators to do light KYC and confirm their project meets Crossmint's [content policy](https://drive.google.com/file/d/1kXv5gX7C8xHJFDAnCQTNUmXrDP05YYk1/view). There are multiple options to achieve this:
* You can conduct project verification yourself.
* You can send creators to register their collection at Crossmint.com and share their collectionID with you, so you can pass it to the digital asset checkout.
* (Coming soon) You can use Crossmint's collection verification SDK to verify creators directly on your site.
# Mint to Specific Template
Source: https://docs.crossmint.com/payments/advanced/mint-to-specific-template
Specify which NFT template to mint from during checkout (for Managed collections only)
## Overview
When minting NFTs from a collection with multiple templates, you can specify which template to mint to using your Crossmint integration. This feature must be explicitly enabled in the Crossmint Console.
This gives you control over exactly which template gets minted when processing a purchase, rather than relying on random template selection.
## Supported Collection Types
Selecting a specific token template for purchase is supported on:
* EVM ERC-721 and ERC-1155 collections
* Solana collections
In the following chains, tokens will always be selected randomly:
* Aptos collections
## Enabling Template Minting
Follow these steps in the Crossmint Console to enable template-specific minting:
1. Go to your collection in the Crossmint Console
2. Navigate to the "Checkout" section
3. Under "NFT Template Settings", enable "Mint to specific NFT Template"
## Using Template IDs
If template-specific minting is disabled for your collection and you pass a template ID in the collection locator,
the request will select a template randomly. Make sure to enable this feature in the Console before using template IDs.
To see all your available templates, navigate to the "NFTs" section in the Console for your managed collection.
You can also get all your available templates for a collection using the [NFT Template API](/api-reference/minting/template/get-all-templates).
Once enabled, specify which template to mint by adding the template ID to your collection locator. The collection locator format without templateId is:
```
crossmint:
```
and with templateId is:
```
crossmint::
```
The `templateId` portion is optional with mint to specific template enabled. If omitted, the system will use your collection's default minting behavior.
Here's how to implement template minting in your integration:
Add the template ID to your collection locator string:
```jsx theme={null}
```
Add the template ID to your collection locator string when creating an order:
```json theme={null}
{
"payment": {
"method": "stripe-payment-element" // or your preferred payment method
},
"lineItems": [
{
// For a specific template:
"collectionLocator": "crossmint::",
// Or for random template selection:
// "collectionLocator": "crossmint:",
"callData": {
"totalPrice": "0.001"
}
}
]
}
```
## Error Handling
When implementing template minting, handle these potential error cases:
1. **Invalid Template ID**
* Error message: Transaction fails with template validation error
* Occurs when: The specified template ID doesn't exist in the collection
* Resolution: Verify the template ID exists in your collection
2. **No Code Storefront**
* Mint to specific template is not currently supported on No Code Storefront. If you'd like this feature, please reach out to your CSE rep
Here's how to handle these errors in your code:
```jsx theme={null}
try {
{
if (error.message.includes(`Template ${templateId} was not found in collection`)) {
// Invalid template ID
throw new Error("Invalid template ID provided");
}
throw error
}}
/>
} catch (error) {
// Handle other errors
console.error("Minting failed:", error);
}
```
```typescript theme={null}
try {
const response = await fetch("/api/create-order", {
method: "POST",
body: JSON.stringify({
lineItems: [
{
collectionLocator: `crossmint:${collectionId}:${templateId}`,
callData: {
totalPrice: "0.001"
}
}
]
})
});
if (!response.ok) {
const error = await response.json();
if (error.message.includes(`Template ${templateId} was not found in collection`)) {
// Invalid template ID
throw new Error("Invalid template ID provided");
}
throw error;
}
} catch (error) {
// Handle the error appropriately
console.error("Order creation failed:", error);
}
```
# Receipt Customization
Source: https://docs.crossmint.com/payments/advanced/receipt-customization
Customize your users' order receipt
Customizing the order receipt that gets sent to your users' email addresses is possible under our Enterprise plan.
## Elements that can be customized
* **Subject**: the subject of the email.
* **Header**: the header of the email.
* **Body**: the body of the receipt within the email.
* **Company logo**: you can add your company logo.
* **Remove Crossmint's links**: you can also remove Crossmint's social links and buttons (included in the default receipt sent).
* **Remove transaction hash**: you can also remove the transaction hash (included in the default receipt sent).
## Elements that cannot be customized
* You cannot include the user's name in the receipt as this information is not always available.
* You cannot remove the "Payment powered by Crossmint" message on the footer for compliance.
Please contact your Crossmint customer success engineer and provide them with the above information.
Here is an example of the default purchase receipt that is emailed to users:
Here is an example of a customized purchase receipt that is emailed to users:
# Collection Registration and Verification API
Source: https://docs.crossmint.com/payments/advanced/registration-and-verification-api
Launchpads now use Crossmint's API to register and verify a collection.
Crossmint provides an API to facilitate collection registration and verification. This functionality is for launchpads that want to use Crossmint's checkout and allow their users to go through the KYC and collection verification flow.
## Before you get started:
Contact your Crossmint customer success engineer to request the `collections.write` scope be added to an existing API key. You will need to provide the projectId associated with the API key you will be using.
Your contract also needs to meet certain requirements. Please refer to the [Register External Collection](/payments/guides/register-collection) documentation page for further information.
Please do not share your API key secret with the Crossmint team or anyone else.
## Collection Registration and Verification process
Once a user has created a collection on your launchpad, you can optionally choose to register the collection with Crossmint to enable Credit Card payments. This API abstracts away much of the complexity involved from the user when they perform KYC and the collection verification process.
As the launchpad, you will pass information about the collection as parameters to the API. Crossmint will then return a URL where the user can complete the KYC and collection verification. This API call's structure is as follows:
Sample code for the collection registration and verification API. Click on "Response" to see what a successful response will look like.
```jsx JavaScript theme={null}
// Define the API URL
const apiUrl = "https://staging.crossmint.com/api/v1-alpha1/collections";
// Set up the request body with actual data instead of placeholders
const requestBody = {
chain: "ethereum", // replace with actual chain name
contractType: "erc-721", // choose between "erc-721" or "erc-1155" or "thirdweb-drop" or "candy-machine"
args: {
contractAddress: "0x123...", // replace with actual contract address
mintFunctionName: "mintUSDC(address,uint256)", // specify the mint function
abi: [], // provide the actual contract ABI array
toParamName: "toAddress", // specify the 'to' parameter name in the mint function
erc20MintCurrency: "usdc", // the ERC20 currency to be used for minting
},
metadata: {
title: "", // replace '' with the actual collection name
description: "", // replace `` with the actual collection's description
imageUrl: "", // replace '' with actual URL to an image
},
ownership: "external", // optional - if you are regestering the collection on behalf of a creator or yourself, choose "external". Else, choose "self".
category: "art", // specify the verification category - this expedites the collection review
scopes: ["payments:credit-card"], // required scopes- "payments:cross-chain" or "payments:credit-card", must specify at least one
};
// Set up the request options
const requestOptions = {
method: "POST",
headers: {
'Content-Type': 'application/json',
'X-API-KEY': '', // replace '' with the API Key that has the scope `collections.write`
},
body: JSON.stringify(requestBody), // Convert the JavaScript object to a JSON string
};
// Make the fetch request
fetch(apiUrl, requestOptions)
.then((response) => response.json()) // Parsing the JSON response
.then((data) => console.log("Success:", data))
.catch((error) => console.error("Error:", error));
```
```json Response (Successful) theme={null}
{
"collectionId": ""
}
```
The returned URL must be the URL that your user needs to be directed to. The user can complete the verification with that URL.
## Get Collection status
You can check the status of your user's verification with the get collection status endpoint. Upon a successful request, the verification `status` for the `seller` and `collection` may have either of the following values:
* `verified` - The user has successfully completed the verification.
* `failed` - The verification failed.
* `crossmint-review` - It is being reviewed by the Crossmint team.
* `unverified` - The user has not initiated or completed the verification process.
```jsx JavaScript theme={null}
// Replace '' with the collectionId returned in the successful POST response
const collectionId = 'your_collection_id'; // Replace 'your_collection_id' with the actual ID
const apiUrl = `https://staging.crossmint.com/api/v1-alpha1/collections/${collectionId}`;
// Set up the request options
const requestOptions = {
method: 'GET',
headers: {
'X-API-KEY': '', /// replace '' with your API Key
'Content-Type': 'application/json',
}
};
// Make the fetch request
fetch(apiUrl, requestOptions)
.then(response => {
if (!response.ok) {
throw new Error('Network response was not ok: ' + response.statusText);
}
return response.json(); // Parse the JSON from the response
})
.then(data => console.log('Success:', data)) // Handle the parsed data
.catch(error => console.error('Error:', error)); // Handle any errors
```
```json Response (Successful) theme={null}
{
"verificationStatus": {
"seller": {
"status": "verified"
},
"collection": {
"status": "verified"
}
}
}
```
***
## FAQs
You can find all the possible inputs below:
```json VerificationCategory inputs theme={null}
VerificationCategory {
LOYALTY: "loyalty",
ART: "art",
MUSIC: "music",
GAMING: "gaming",
TICKETING: "ticketing",
CHARITY: "charity",
OTHER: "other",
}
```
The verification requirement varies by the payment type selected for the collection.
* Credit card: Seller and collection status must be “verified”.
* Crypto: Only collection status must be “verified”.
# Checking Supported Tokens
Source: https://docs.crossmint.com/payments/advanced/supported-tokens
Learn how to check if a memecoin or token is supported by Crossmint Checkout
## Introduction
Before integrating a memecoin or token using Crossmint's checkout, it's important to verify that it's supported by Crossmint. This guide will show you how to use the Crossmint API to check if a specific token is supported and what features are available for it.
## Prerequisites
Get your API keys from the [Crossmint Console](https://www.crossmint.com/console/projects/apiKeys)
[More info on creating API keys here](/introduction/platform/api-keys#how-to-get-and-use-api-keys)
Make sure the `orders.create` scope is enabled for your API key.
## Getting Started
The Supported Tokens API allows you to query the status of any token to determine if it's supported by Crossmint and what features are available.
* **Endpoint**: `https://www.crossmint.com/api/v1-alpha2/tokens/:tokenLocator`
* **Method**: GET
* **Authentication**: Requires either a server or client API key
### Token Locator Format
Memecoin checkout supports the **Solana** and **Base** networks.
The token locator follows this format:
```
network:tokenAddress
```
Example:
* **Staging**: Use the XMEME test token: `solana:7EivYFyNfgGj8xbUymR7J4LuxUHLKRzpLaERHLvi7Dgu`
* **Production**: Use any supported token, e.g. TRUMP: `solana:6p6xgHyF7AeE6TZkSmFsko444wqoP15icUSqi2jfGiPN`
Production memecoin tokens **will not work** in the **staging** environment.
## Checking Token Support
### Using JavaScript
Here's how to check if a token is supported using JavaScript:
```javascript theme={null}
// Example usage
const apiKey = "your-api-key"; // Replace with your actual API key
const tokenLocator = "solana:6p6xgHyF7AeE6TZkSmFsko444wqoP15icUSqi2jfGiPN"; // Example: TRUMP token
const checkTokenSupport = async (apiKey, tokenLocator) => {
try {
const response = await fetch(`https://www.crossmint.com/api/v1-alpha2/tokens/${tokenLocator}`, {
method: "GET",
headers: {
"x-api-key": apiKey,
"Content-Type": "application/json",
},
});
const data = await response.json();
return data;
} catch (error) {
console.error("Error checking token support:", error);
throw error;
}
};
checkTokenSupport(apiKey, tokenLocator)
.then((result) => {
console.log("Token support details:", result);
if (result.available) {
console.log("This token is supported by Crossmint!");
} else {
console.log("This token is not currently supported.");
}
console.log("Available features:", result.features);
})
.catch((err) => {
console.error("Failed to check token support:", err);
});
```
### Sample Response
A successful response will look similar to this:
```json theme={null}
{
"token": "solana:6p6xgHyF7AeE6TZkSmFsko444wqoP15icUSqi2jfGiPN",
"available": true,
"features": {
"creditCardPayment": true
}
}
```
## Integrating with Your Checkout Flow
Once you've confirmed that a token is supported, you can use the information from the API response to customize your checkout experience.
### Example: Pre-checkout Validation
Here's an example of how to incorporate token support checking before initiating a checkout:
```javascript theme={null}
const initiateCheckout = async (apiKey, tokenLocator, amount, recipientWallet) => {
// First, check if the token is supported
const tokenSupport = await checkTokenSupport(apiKey, tokenLocator);
if (!tokenSupport.available) {
throw new Error("This token is not currently supported by Crossmint.");
}
if (!tokenSupport.features.creditCardPayment) {
throw new Error("Credit card payment is not available for this token.");
}
// If we get here, the token is supported with credit card checkout
// Proceed with creating an order
const orderResponse = await fetch("https://www.crossmint.com/api/2022-06-09/orders", {
method: "POST",
headers: {
"x-api-key": apiKey,
"Content-Type": "application/json",
},
body: JSON.stringify({
lineItems: [
{
tokenLocator: tokenLocator,
executionParameters: {
mode: "exact-in",
amount: amount,
maxSlippageBps: "500",
},
},
],
payment: {
method: "stripe-payment-element",
receiptEmail: "user@example.com", // Replace with user's email
},
recipient: {
walletAddress: recipientWallet,
},
}),
});
return await orderResponse.json();
};
```
## Best Practices
1. **Cache Results (with caution)**: Token support status can change, but for frequently accessed tokens, consider caching results for a short period (e.g., 1 hour) to reduce API calls.
2. **Handle Errors Gracefully**: Implement proper error handling to provide clear feedback to users when a token is not supported.
3. **Check Before Displaying Options**: Only display payment methods that are actually available for the token.
## Next Steps
Now that you can check token support, learn how to implement a credit card checkout for memecoins
Understand the lifecycle of orders in Crossmint's checkout flow
## FAQ
Yes, if you'd like to request support for a token that is currently unsupported, contact our [support team](https://help.crossmint.com).
Transactions for unsupported tokens will fail. This is why it's crucial to check token support before initiating
a checkout process.
No, each token may have different available payment features. The API response includes a `features` object that
specifies which payment methods are available for each token.
The API is rate-limited to 50 requests per second. For most applications, this should be more than sufficient.
# Testing Tips
Source: https://docs.crossmint.com/payments/advanced/testing-tips
This document is intended to share some of the best practices and tips for testing.
## Limits in Staging
In order to preserve testnet ETH/tokens, Crossmint imposes very low upper bound limits on NFT prices in the staging
environment. The default limit on production is USD $0.75 - $1,500 per transaction.
To avoid issues, set the `totalPrice` and the onchain NFT price to no more than:
* Ethereum (Sepolia): 0.000005 ETH (this applies for other EVM chains as well)
* Polygon (Amoy): 0.005 MATIC
* Solana (Devnet): 0.001 SOL
* USDC (all chains): \$10
* Other chains: the equivalent of \$0.10 in the native token
The minimum credit card charge amount is \$0.50. Even though the price of the NFT at the above rates will often be
less the \$0.01, the checkout will still show \$0.50. This will be less of an issue on production when you're
charging full price.
## Test Credit Card Numbers
When testing the Checkout and credit card payments on staging you'll need to use test card numbers. You can use these cards to test the flow end-to-end without actually processing real payments.
You can use the following test credit card numbers:
* `4242424242424242` (Visa)
* `4000056655665556` (Visa Debit)
* `5555555555554444` (Mastercard)
* `5200828282828210` (Mastercard Debit)
For `CVV` or `CVC`, use any 3 digit number
For `Expiry Date` or `MM/YY`, enter any future date
### Advanced Card Testing
If you want to simulate purchases from [different countries](https://stripe.com/docs/testing?testing-method=card-numbers#international-cards), [decline conditions](https://stripe.com/docs/testing?testing-method=card-numbers#declined-payments), or other [brands](https://stripe.com/docs/testing?testing-method=card-numbers#cards) refer to the [Stripe documentation](https://stripe.com/docs/testing?testing-method=card-numbers) for more test card numbers.
## Reviewing Orders in the Developer Console
You can get insights into the purchases being made against your collection by navigating to the Orders tab for your collection in the Developer Console. You can use this to get insights about orders in production, but also to see the status of orders when testing out your integration in the staging environment.
When looking for pre-completion phase orders while testing [Headless Checkout](/payments/headless/overview), you'll
need to enter the order ID in the search box. This is because the console view purposefully has a reduced list of
status options for filtering.
# NFT Drops Priced in USDC
Source: https://docs.crossmint.com/payments/advanced/usdc-support
How to set up Crossmint if your collection is denominated in USDC
By denominating your collection in USDC, you can eliminate price volatility, and offer a more accessible experience
to users.
## Ensure you are using a supported USDC token
First of all, make sure your collection is denominated in one of the USDC token addresses supported by Crossmint.
Chain
Network
USDC Token Address
USDXM Token Address
USDC.e is only supported on previously registered integrations on Polygon mainnet. Please use the native USDC
address for new projects.
If you need some testnet USDC you can mint it freely from the contract or from this faucet app that uses the same
testnet token contracts as the Checkout tools.
[Crossmint Testnet USDXM Faucet](https://usdc-faucet.vercel.app/)
## Validate your collection meets the requirements
In addition to the standard requirements, keep in mind:
1. **The mint function should be `nonpayable`.**
2. **The USDC token has 6 decimals** instead of 18 like ETH, Matic, and most other tokens.
3. **The `totalPrice` attribute in the `callData` of your `lineItems` should be in units of USDC**. If your intended price is 100 USD, then you will set `totalPrice` to `100` e.g. `totalPrice="100"`.
4. **Your ERC-20 transfer call must request the funds from `msg.sender`** instead of the address parameter. This allows Crossmint to pay from our fleet of treasury wallets and deliver the NFTs directly to the user's wallet. See an example mint function below.
```solidity Solidity theme={null}
function mintUSDC(address _to) public {
// pre payment logic here
// note that you need to transfer from msg.sender, NOT the _to address
tokenInstance.transferFrom(msg.sender, address(this), priceUSDC );
// actual minting logic here
}
```
```tsx theme={null}
```
The `totalPrice` attribute in the `callData` of your `lineItems` should be in units of USDC. If your intended price is 100 USD, then you will set `totalPrice` to `100` e.g. `totalPrice="100"`.
Add a claim phase with currency type `USDC`. In staging. you must use `Custom ERC-20` and specify one of the token address listed above. In mainnet, you should select the default USDC option.
Denominate your candy machine on USDC and Crossmint will automatically detect it. You can learn how on this [guide](https://medium.com/crossmint-tech/how-to-create-a-fixed-price-usdc-nft-drop-on-solana-2ab251e61384).
# Webhooks
Source: https://docs.crossmint.com/payments/advanced/webhooks
Track order lifecycle events and payment status with webhooks
# Webhooks Overview
Webhooks allow you to track the status of payments and order lifecycle events in your application. They provide real-time notifications for various events like payment processing, NFT minting, and order fulfillment.
## Webhook Systems by Checkout Version
Crossmint offers different webhook systems for Checkout V2 and V3:
### Checkout V2 Webhooks (Legacy)
The Checkout V2 webhook system provides basic payment tracking with a single event type:
* `purchase.succeeded` - Triggered when an NFT has been successfully purchased and delivered
### Checkout V3 Webhooks (Current)
The Checkout V3 webhook system offers comprehensive order lifecycle tracking with multiple event types:
**Quote Phase**
* `orders.quote.created` - Triggered when a new order is created
* `orders.quote.updated` - Triggered when order details are modified
**Payment Phase**
* `orders.payment.succeeded` - Triggered when payment is successfully processed
* `orders.payment.failed` - Triggered when payment fails
**Delivery Phase**
* `orders.delivery.initiated` - Triggered when delivery begins
* `orders.delivery.completed` - Triggered when delivery succeeds
* `orders.delivery.failed` - Triggered when delivery fails
## Setting Up Webhooks
### 1. Create an endpoint route
Using a standard nodejs API server, create an endpoint.
You can test locally by installing [ngrok](https://ngrok.com/docs/getting-started/) and creating a routed endpoint
to a specified port. > **Note**: Use ngrok only for testing. In production, ensure your endpoint is properly secured
with HTTPS and appropriate access controls.
### 2. Configure the endpoint
Your endpoint should:
* Handle POST requests only
* Parse webhook events from the request body
* Respond with a `200` status code to acknowledge receipt
Your server must return a 2xx HTTP status quickly so the webhook is marked as delivered.
Example handler:
```javascript theme={null}
// endpoint.js
export default function handler(req, res) {
if (req.method === "POST") {
console.log(`[webhook] Event received:`, req.body);
}
res.status(200).json({});
}
```
Don't be strict with payload validations as Crossmint may add new fields to the webhooks as products evolve.
Your server must return a 2xx HTTP status quickly so the webhook is marked as delivered.
### 3. Example Webhook Responses
```json EVM theme={null}
{
"type": "purchase.succeeded",
"status": "success",
"walletAddress": "",
"projectId": "",
"collectionId": "",
"clientId": "",
"txId": "",
"contractAddress": "",
"tokenIds": [], // only present for EVM collections
"passThroughArgs": "" // only if whPassThroughArgs set
}
```
```json Solana theme={null}
{
"type": "purchase.succeeded",
"status": "success",
"walletAddress": "",
"clientId": "",
"txId": "",
"mintAddress": "",
"passThroughArgs": ""
}
```
```json theme={null}
{
"type": "orders.quote.created",
"payload": {
"totalPrice": {
"amount": "0.50",
"currency": "usd"
},
"lineItems": [
{
"metadata": {
"title": "Collection Name",
"description": "Collection Description",
"imageUrl": "https://..."
},
"price": {
"amount": "0.50",
"currency": "usd"
},
"quantity": 1
}
]
}
}
```
```json theme={null}
{
"type": "orders.payment.succeeded",
"payload": {
"orderIdentifier": "7723139d-fba3-474d-8e52-0ac7512d5c7b",
"paymentMethodType": "credit-card",
"totalPrice": {
"amount": "0.50",
"currency": "usd"
}
}
}
```
```json theme={null}
{
"type": "orders.delivery.completed",
"payload": {
"orderIdentifier": "7723139d-fba3-474d-8e52-0ac7512d5c7b",
"lineItems": [
{
"delivery": {
"status": "completed",
"recipient": {
"locator": "email:user@example.com:polygon",
"email": "user@example.com",
"walletAddress": "0x1234..."
},
"txId": "0x2e69f11dae7869b92e3d5eaf4cadd50c48b5c6803d1232815f979d744521ad4c",
"tokens": [
{
"locator": "polygon:0xE04Cf294985282Ddc088E6433c064cfB85eD9EdA:3",
"contractAddress": "0xE04Cf294985282Ddc088E6433c064cfB85eD9EdA",
"tokenId": "3",
"quantity": "1500000",
"symbol": "TOKEN",
"decimals": 6
}
]
}
}
]
}
}
```
The `quantity`, `symbol`, and `decimals` fields are optional and only present when applicable (e.g., for fungible token purchases in exact-in execution mode).
### 4. Pass Custom Arguments (Optional)
Custom arguments (whPassThroughArgs) are only supported in Checkout V2 webhooks. This feature is not available in
Checkout V3 webhooks (orders.\*).
You can pass custom arguments through Checkout V2 webhooks to track additional information:
* User IDs (If you want additional security, sign this ID with a custom key, or send it as a signed JWT, and verify its integrity later on your server)
* Timestamps
* Product SKUs
* Custom metadata
Example of passing arguments:
```jsx theme={null}
function NFTSalePage() {
const whArgs = {
uid: 123424,
sku: 123123123,
metadata: { custom: "data" },
};
const whArgsSerialized = JSON.stringify(whArgs);
return (
);
}
```
Then, extract them on the server:
```javascript theme={null}
export default function handler(req, res) {
const { whPassThroughArgs } = req.body;
if (whPassThroughArgs) {
const whArgsDeserialized = JSON.parse(whPassThroughArgs);
console.log(whArgsDeserialized);
}
res.status(200).json({});
}
```
### 5. Pre & Post Processing
Add your pre and post processing logic when setting up your webhook listener. For example, you can call back to your database when a certain id has succeeded or even use Sendgrid or EmailJS to send an email to a recipient when a mint completes.
### 6. Configure in Crossmint Console
1. Navigate to the [Webhooks page](https://www.crossmint.com/console/webhooks) in the console
2. Click **Add Endpoint**
3. Enter your endpoint URL
4. Select the webhook events to receive
5. Click **Create**
### 6. Security
For security, verify webhook signatures using the signing secret from your endpoint details page:
See the [Verify Webhooks](/introduction/platform/webhooks/verify-webhooks) guide for implementation details.
## Testing Webhooks
1. Use test card number `4242 4242 4242 4242` for successful payments
2. Use `4000 0000 0000 4954` to test payment failures
3. Monitor webhook deliveries in the [Console](https://www.crossmint.com/console/webhooks)
# Localization
Source: https://docs.crossmint.com/payments/embedded/customize/localization
Customize the language and currency displayed on checkout
## How to Adjust the Currency and Language
To customize the language use the property `locale` and for currency, set `defaultCurrency`. Here's an example:
```tsx {3,16} theme={null}
```
# Apple Pay Integration Guide
Source: https://docs.crossmint.com/payments/embedded/guides/apple-pay
Learn how to enable Apple Pay on your site for Checkout
To enable Apple Pay on your site, follow these steps using the [Crossmint Developer Console](https://www.crossmint.com/console/apple-pay-domains).
## Prerequisites
1. **HTTPS Hosting**: Your site must be hosted on HTTPS to ensure secure transactions.
2. **Crossmint Account**: You need an active Crossmint developer account.
## Setup Instructions
### Step 1: Download the Verification File
1. Go to the [Apple Pay Domains](https://www.crossmint.com/console/apple-pay-domains) page in the Crossmint Console.
2. Click **Download file** to get the Apple Developer Merchant ID Domain Association file.
### Step 2: Host the Verification File
Host the downloaded file on your server at the following path:
```
https://[your-domain]/.well-known/apple-developer-merchantid-domain-association
```
**Option 1: Using the Public Folder**
1. Create the `.well-known` folder inside your `public` directory.
2. Place the downloaded file in `public/.well-known/` with the filename `apple-developer-merchantid-domain-association` (no extension).
**Option 2: Using a Route**
1. Create a folder at `app/.well-known/apple-developer-merchantid-domain-association/`
2. Add a `route.ts` file with the following content:
```typescript route.ts theme={null}
import { NextResponse } from "next/server";
export async function GET() {
return new NextResponse("PASTE_FILE_CONTENT_HERE", {
headers: {
"Content-Type": "text/plain",
},
});
}
```
Replace `PASTE_FILE_CONTENT_HERE` with the content of the downloaded verification file.
1. Create the `.well-known` folder inside your `public` directory.
2. Place the downloaded file in `public/.well-known/` with the filename `apple-developer-merchantid-domain-association` (no extension).
1. Create the `.well-known` folder inside your `public` directory.
2. Place the downloaded file in `public/.well-known/` with the filename `apple-developer-merchantid-domain-association` (no extension).
### Step 3: Verify Your Domain
1. Return to the [Apple Pay Domains](https://www.crossmint.com/console/apple-pay-domains) page in the Crossmint Console.
2. Enter your domain (e.g., `example.com` or `checkout.example.com`) in the input field.
3. Click **Verify domain**.
If verification succeeds, your domain will appear in the list of registered domains and Apple Pay will be enabled for that domain.
If verification fails, ensure the file is publicly accessible at the correct path. You can test by visiting `https://your-domain/.well-known/apple-developer-merchantid-domain-association` in your browser.
## Local Testing
Using [ngrok](https://ngrok.com/) is recommended for local testing. Ngrok allows you to expose your local server to the internet securely.
1. Start ngrok with a fixed domain if possible for consistent testing.
2. Host the verification file on your local server.
3. Register the ngrok domain in the Crossmint Console.
4. Test Apple Pay on Safari (macOS) or any iOS device.
***
For further assistance, please contact our support team.
# Use Connected Wallet as Payer
Source: https://docs.crossmint.com/payments/embedded/guides/custom-payer
Learn how to configure an already connected wallet as the payer wallet
## Overview
By default, Embedded checkout includes wallet connector UI. However, if your customers have already
connected their wallet to your website, you can configure that connected wallet directly as the payer.
This is useful when:
* You're already using a wallet connection solution (like RainbowKit, Solana Wallet Adapter, Web3Modal, or ConnectKit)
* You are using embedded wallets (from Crossmint or other providers)
## Quick Integration
Here's a simple example using [wagmi](https://wagmi.sh/), [viem](https://viem.sh/) and [TanStack Query](https://tanstack.com/query/v5):
Make sure you have this environment variables on `.env.local` or directly replace it in the code `bash
NEXT_PUBLIC_CLIENT_API_KEY="__YOUR_CLIENT_KEY__" # From API Keys page NEXT_PUBLIC_RECIPIENT_WALLET_ADDRESS="" #
Address receiving the assets NEXT_PUBLIC_RECEIPT_EMAIL="user@example.com"
NEXT_PUBLIC_COLLECTION_ID="__YOUR_COLLECTION_ID__" # From Collection details page `
```tsx Checkout.tsx theme={null}
import { CrossmintEmbeddedCheckout } from "@crossmint/client-sdk-react-ui";
import { Hex, parseTransaction } from "viem";
import { useAccount, useWalletClient } from "wagmi";
import { baseSepolia, polygonAmoy } from "wagmi/chains";
export default function Checkout() {
const { address } = useAccount();
const { data: walletClient } = useWalletClient();
const collectionId = process.env.NEXT_PUBLIC_COLLECTION_ID as string;
const chainIds: Record = {
"base-sepolia": baseSepolia.id,
"polygon-amoy": polygonAmoy.id,
} as const;
// Don't render checkout if wallet is not connected
if (!address || !walletClient) {
return (
);
}
```
```tsx Home.tsx theme={null}
"use client";
import { CrossmintProvider } from "@crossmint/client-sdk-react-ui";
import { WagmiProvider, createConfig, http } from "wagmi";
import { baseSepolia, polygonAmoy } from "wagmi/chains";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { injected } from "wagmi/connectors";
import Checkout from "@/components/Checkout";
import WalletConnector from "@/components/WalletConnector";
export default function Home() {
const clientApiKey = process.env.NEXT_PUBLIC_CLIENT_API_KEY as string;
// Create a client
const queryClient = new QueryClient();
// Create wagmi config
const config = createConfig({
chains: [baseSepolia, polygonAmoy] as any,
connectors: [injected()] as any,
transports: {
[baseSepolia.id]: http(),
[polygonAmoy.id]: http(),
},
} as any);
return (
);
}
```
When the user clicks the hosted checkout button, our SDK will first call `handleChainSwitch` to ensure the correct
network is set, then `handleSignAndSendTransaction` to complete the purchase.
## Payer Configuration
The `payer` prop accepts the following configuration:
| Property | Type | Description |
| ------------------------------ | -------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `address` | `string` | The wallet address of the payer |
| `initialChain` | `PayerSupportedBlockchains` | The initial blockchain (e.g. "polygon", "ethereum", "base-sepolia") |
| `supportedChains` | `PayerSupportedBlockchains[]` | Optional list of supported chains. Defaults to `initialChain` if not specified |
| `handleChainSwitch` | `(chain: PayerSupportedBlockchains) => Promise` | Function to handle chain switching |
| `handleSignAndSendTransaction` | `(serializedTx: string) => Promise` | Function to handle transaction signing and sending, where `TransactionResponse` is either `{ success: true, txId: string }` or `{ success: false, errorMessage: string }` |
## Popular Wallet Solutions
You can integrate Crossmint with popular Web3 solutions:
### RainbowKit
```tsx theme={null}
import { useConnectModal } from "@rainbow-me/rainbowkit";
// Similar implementation as above, but using RainbowKit's hooks
```
### Web3Modal
```tsx theme={null}
import { useWeb3Modal } from "@web3modal/react";
// Similar implementation as above, but using Web3Modal's hooks
```
## Best Practices
1. **Error Handling**: Always return clear error messages in `handleSignAndSendTransaction`
2. **Chain Support**: Be explicit about supported chains to avoid runtime errors
3. **User Experience**: Show loading states during chain switches and transactions
4. **Wallet Connection**: Ensure wallet is connected before showing checkout
## Related Resources
* [Payment Methods](/payments/embedded/guides/payment-methods)
* [React Hooks](/payments/embedded/guides/hooks)
* [Testing Tips](/payments/advanced/testing-tips)
# Google Pay Mobile
Source: https://docs.crossmint.com/payments/embedded/guides/google-pay
Learn how to enable Google Pay for token checkout and onramp flows using Crossmint's mobile SDKs
**This guide is for mobile apps only.** For web applications, Google Pay works automatically when enabled via the `payment.fiat.allowedMethods.googlePay` prop—no additional setup required. See [Payment Methods](/payments/embedded/guides/payment-methods) for web configuration.
This guide covers how to enable Google Pay for token checkout and onramp flows in **React Native** and **Android native (Kotlin)** applications. Mobile apps require additional native configuration that web apps don't need.
## Overview
Crossmint's mobile SDKs provide pre-built checkout components that support Google Pay out of the box. When properly configured, users see the native Android Google Pay sheet, providing seamless access to payment credentials stored in Google Wallet without leaving your app.
### Mobile vs Web
| Platform | Setup Required | Guide |
| -------------------------- | ---------------------- | ------------------------------------------------------------ |
| Web (React, Next.js, etc.) | Enable via props only | [Payment Methods](/payments/embedded/guides/payment-methods) |
| React Native (Expo) | Expo plugin + rebuild | This guide |
| React Native (Bare) | Manual Android config | This guide |
| Android Native (Kotlin) | AndroidManifest config | This guide |
### Supported Use Cases
* Token purchases (buy tokens with fiat)
* Stablecoin onramp (USDC, etc.)
* NFT checkout
## Mobile SDK Integration
### Prerequisites
Before starting, ensure you have:
* React Native project with Expo or bare workflow
* Crossmint account with API keys (staging and/or production)
* Android device or emulator with Google Play Services
```shell theme={null}
pnpm add @crossmint/client-sdk-react-native-ui
```
The SDK includes `react-native-webview` as a dependency. If you need to install it separately, ensure version **13.15.0 or higher**:
```shell theme={null}
pnpm add react-native-webview@^13.15.0
```
Update your `app.json` to enable Google Pay:
```json theme={null}
{
"expo": {
"plugins": [
[
"@crossmint/client-sdk-react-native-ui",
{
"enableGooglePay": true
}
]
]
}
}
```
This plugin modifies native Android permissions required for Google Pay. The app must be rebuilt after adding this configuration.
Hot reload cannot apply the permission changes. You must rebuild:
```shell theme={null}
npx expo run:android
```
Or for bare React Native:
```shell theme={null}
npx react-native run-android
```
Use `CrossmintEmbeddedCheckout` for token purchases:
```javascript theme={null}
import {
CrossmintEmbeddedCheckout,
CrossmintProvider,
} from "@crossmint/client-sdk-react-native-ui";
export default function CheckoutScreen() {
return (
);
}
```
For staging/testing, use the XMEME test token:
```javascript theme={null}
tokenLocator: "solana:7EivYFyNfgGj8xbUymR7J4LuxUHLKRzpLaERHLvi7Dgu"
```
Hide destination and email inputs for a cleaner UI:
```javascript theme={null}
```
### Prerequisites
* Minimum Android SDK version 24 (Android 7.0)
* Jetpack Compose
* Crossmint account with API keys
In your app-level `build.gradle.kts`:
```kotlin theme={null}
dependencies {
implementation("com.crossmint:crossmint-sdk-android:0.0.9")
}
```
Add the required query intents to enable Google Pay functionality:
```xml theme={null}
```
**Security requirement:** Order creation must happen on your backend to protect your API key.
```bash cURL theme={null}
curl --location 'https://staging.crossmint.com/api/2022-06-09/orders' \
--header 'x-api-key: YOUR_SERVER_API_KEY' \
--header 'Content-Type: application/json' \
--data-raw '{
"recipient": {
"walletAddress": "YOUR_WALLET_ADDRESS"
},
"payment": {
"receiptEmail": "user@example.com",
"method": "card"
},
"lineItems": {
"tokenLocator": "solana:TOKEN_ADDRESS",
"executionParameters": {
"mode": "exact-in",
"amount": "1"
}
}
}'
```
```javascript Node.js theme={null}
const url = 'https://staging.crossmint.com/api/2022-06-09/orders';
const payload = {
recipient: {
walletAddress: "YOUR_WALLET_ADDRESS"
},
payment: {
receiptEmail: "user@example.com",
method: "card"
},
lineItems: {
tokenLocator: "solana:TOKEN_ADDRESS",
executionParameters: {
mode: "exact-in",
amount: "1"
}
}
};
const options = {
method: 'POST',
headers: {
'x-api-key': 'YOUR_SERVER_API_KEY',
'Content-Type': 'application/json'
},
body: JSON.stringify(payload)
};
const response = await fetch(url, options);
const data = await response.json();
console.log(data);
```
```python Python theme={null}
import requests
url = "https://staging.crossmint.com/api/2022-06-09/orders"
payload = {
"recipient": {
"walletAddress": "YOUR_WALLET_ADDRESS"
},
"payment": {
"receiptEmail": "user@example.com",
"method": "card"
},
"lineItems": {
"tokenLocator": "solana:TOKEN_ADDRESS",
"executionParameters": {
"mode": "exact-in",
"amount": "1"
}
}
}
headers = {
"x-api-key": "YOUR_SERVER_API_KEY",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.json())
```
The response includes `orderId` and `clientSecret` needed for the checkout component.
```kotlin theme={null}
import com.crossmint.kotlin.checkout.CheckoutEnvironment
import com.crossmint.kotlin.checkout.CrossmintEmbeddedCheckout
import com.crossmint.kotlin.checkout.models.*
@Composable
fun CheckoutScreen(orderId: String, clientSecret: String) {
CrossmintEmbeddedCheckout(
orderId = orderId,
clientSecret = clientSecret,
payment = CheckoutPayment(
crypto = CheckoutCryptoPayment(enabled = false),
fiat = CheckoutFiatPayment(
enabled = true,
allowedMethods = CheckoutAllowedMethods(
googlePay = true,
applePay = false,
card = true
)
)
),
appearance = CheckoutAppearance(
rules = CheckoutAppearanceRules(
destinationInput = CheckoutDestinationInputRule(display = "hidden"),
receiptEmailInput = CheckoutReceiptEmailInputRule(display = "hidden")
)
),
environment = CheckoutEnvironment.STAGING,
modifier = Modifier.fillMaxSize()
)
}
```
## Google Pay Production Approval
Google Pay works immediately in **staging** environments. For **production**, Google requires explicit approval of your app.
1. Navigate to the [Google Pay & Wallet Console](https://pay.google.com/business/console)
2. Select "Merchant" as your business type
3. Complete all business profile information
4. Navigate to **Google Pay API** and click "Get Started"
5. Accept the Google Pay API Terms of Service
6. Note your **Merchant ID** (top-right corner after completion)
In the Google Pay & Wallet Console:
1. Navigate to **Google Pay API** then **Integrations** then **Integrate with your Android app**
2. Locate your Android application and click "Manage"
3. Select your integration type (typically "Gateway")
4. Upload screenshots of your TEST Google Pay integration
5. Click "Save" then "Submit for approval"
**Required Screenshots:**
* Product/item selection showing Google Pay option
* Cart or checkout view with payment options
* Google Pay payment sheet with card selection
* Confirmation or receipt screen
After Google approval (typically \~1 business day):
1. Sign your APK with a **release key** (debug keys don't work in production)
2. Update environment settings:
* Kotlin: `environment = CheckoutEnvironment.PRODUCTION`
* React Native: Use production API key
3. Publish your app to Google Play Store
## SDK Version Requirements
| Component | Minimum Version |
| ------------------------------------------------ | --------------- |
| `@crossmint/client-sdk-react-native-ui` | Latest |
| `react-native-webview` (if installed separately) | 13.15.0 |
| `com.crossmint:crossmint-sdk-android` | Latest |
| Google Play Services (user device) | 25.18.30 |
## Troubleshooting
1. **Plugin not configured:** Verify `enableGooglePay: true` in `app.json` plugins
2. **App not rebuilt:** Run `npx expo run:android` after adding the plugin
3. **Environment mismatch:** Staging works without approval; production requires it
4. **Payment method disabled:** Check `googlePay: true` in `allowedMethods`
1. **AndroidManifest missing queries:** Add all three Chromium intent queries
2. **Native rebuild required:** Hot reload cannot apply permission changes
3. **Device requirements:** User needs Google Play Services 25.18.30+ and WebView 137+
1. Check that your API key is valid for the environment (staging vs production)
2. Verify the `tokenLocator` is correct and available
3. Ensure the quote hasn't expired (quotes have time limits)
**"Token purchase is only available in production":** Some tokens only work in production. Use the XMEME test token for staging: `7EivYFyNfgGj8xbUymR7J4LuxUHLKRzpLaERHLvi7Dgu`
## Testing Guidance
* Use staging API keys from your Crossmint console
* Google Pay functions immediately after proper configuration
* Use XMEME test token for token testing
* Test with real Google accounts and saved payment methods
* Requires completed Google approval process
* Sign APK with release key
* Use production API keys
* Real charges may occur - test with small amounts
## Useful Resources
Get started with token checkout in React Native
Manage your Google Pay merchant account
Example React Native Expo project with checkout
Example Android Kotlin project with checkout
# React Hooks
Source: https://docs.crossmint.com/payments/embedded/guides/hooks
Learn how to use React hooks to control and customize your Embedded Checkout experience
## Complete Example
Here's a full example showing how to implement a custom checkout experience:
```tsx theme={null}
import {
CrossmintProvider,
CrossmintCheckoutProvider,
CrossmintEmbeddedCheckout,
useCrossmintCheckout,
} from "@crossmint/client-sdk-react-ui";
function CheckoutPage() {
return (
);
}
function CheckoutStatus() {
const { order } = useCrossmintCheckout();
if (!order) {
return
Loading...
;
}
switch (order.phase) {
case "completed":
return
Purchase complete!
;
case "delivery":
return
Delivering your NFTs...
;
case "payment":
return
Processing payment...
;
case "quote":
return
Preparing your order...
;
}
}
```
## Migration from Previous Version
If you're migrating from the previous version, note these key differences:
* The event system has been replaced with the `useCrossmintCheckout` hook
* Order status is now accessed through the hook instead of event listeners
* Custom UI is built using React components instead of event handlers
* Components must be wrapped with required providers
The new hook-based approach provides a more React-friendly experience and better TypeScript support compared to the
previous event system.
## Related Resources
* [UI Customization](/payments/embedded/guides/ui-customization)
* [Payment Methods](/payments/embedded/guides/payment-methods)
* [Order Lifecycle](/payments/headless/guides/order-lifecycle)
* [Testing Tips](/payments/advanced/testing-tips)
# Item Selection
Source: https://docs.crossmint.com/payments/embedded/guides/item-selection
Specify which items to purchase with Embedded Checkout
The Embedded Checkout allows you to specify which items your users can purchase using the `lineItems` property. This guide explains how to configure item selection for different asset types.
## Item Selection Examples
```tsx collectionLocator theme={null}
```
```tsx collectionLocator - imported contract theme={null}
```
```tsx tokenLocator - EVM theme={null}
::
callData: {
totalPrice: "25.00", // Only totalPrice is required for token purchases
// For external EVM contracts, you might need additional parameters:
// _amount: 1, // if your contract uses _amount instead of quantity
// tokenId: "1234", // if your contract requires explicit tokenId
// metadata: "ipfs://...", // if your contract accepts metadata
},
}}
/>
```
```tsx tokenLocator - Solana theme={null}
:
callData: {
totalPrice: "25.00",
buyerCreatorRoyaltyPercent: 100, // Required for Solana
},
}}
/>
```
```tsx productLocator theme={null}
```
## Accessing Item Data in React Components
When using Embedded Checkout, you can access the current order's item data using the `useCrossmintCheckout` hook:
```tsx theme={null}
import { useCrossmintCheckout } from "@crossmint/client-sdk-react-ui";
function YourComponent() {
const { order } = useCrossmintCheckout();
// Access line items data
const lineItems = order?.lineItems || [];
return (
);
}
```
The `useCrossmintCheckout` hook must be used within components wrapped by both `CrossmintProvider` and
`CrossmintCheckoutProvider`.
# Payment Methods
Source: https://docs.crossmint.com/payments/embedded/guides/payment-methods
Enable a complete payment suite for your digital asset checkout - from traditional credit cards and digital wallets to cross-chain crypto payments, all through a single integration
## Overview
Crossmint's Embedded Checkout makes it easy to accept multiple payment methods. Users can pay:
* With credit/debit cards
* Using Apple Pay or Google Pay
* Using ETH, SOL, USDC, EURC, or other supported tokens
* Using cross-chain crypto on supported chains
## Quick Integration
Enable all payment methods with just a few lines of code:
```jsx theme={null}
```
## Fiat Payments
### Credit Cards & Digital Wallets
Embedded Checkout provides a seamless fiat payment experience supporting:
* All major credit and debit cards
* Apple Pay for iOS/Safari users
* Google Pay for Android/Chrome users
You can configure which fiat payment methods to enable:
```jsx theme={null}
payment={{
fiat: {
enabled: true,
// By default, all payment methods are enabled if you don't specify any.
allowedMethods: {
card: true, // Enable/disable credit cards
applePay: true, // Enable/disable Apple Pay
googlePay: true, // Enable/disable Google Pay
},
defaultCurrency: "usd" // Set default currency
}
}}
```
## Crypto Payments
### Native Wallet Support
Embedded Checkout works with any Web3 wallet, including:
* MetaMask
* Coinbase Wallet
* WalletConnect
* Phantom
Users can pay with ETH, SOL, stablecoins (USDC, EURC), or other supported tokens on their preferred chain. Enable crypto payments with:
```jsx theme={null}
payment={{
crypto: {
enabled: true,
defaultChain: "ethereum", // Optional: Set default blockchain
defaultCurrency: "usdc" // Optional: Set default currency (usdc, eurc, eth)
}
}}
```
## Cross-Chain Support
The checkout supports paying with funds from any supported blockchain, even if the digital asset is on a different chain. For example, a user could pay for a digital asset on Polygon using ETH from their Ethereum wallet. The checkout handles all necessary conversions automatically.
## Customizing the Default Experience
Set a default payment method to guide users:
```jsx theme={null}
payment={{
defaultMethod: "fiat", // or "crypto"
crypto: {
enabled: true
},
fiat: {
enabled: true
}
}}
```
### Test Price Limits and Test Credit Cards
When building your applications using the staging environment, you can use various test credit cards numbers to see the entire process end-to-end, without actually having to transact using a real credit card. Check out the Testing Tips page for more info on [price limits](/payments/advanced/testing-tips#limits-in-staging) and [test card numbers](/payments/advanced/testing-tips#test-credit-card-numbers).
## Best Practices
* Enable Multiple Payment Methods: Offer both fiat and crypto options to maximize conversion
* Set Default Currency: Choose a default that matches your target market
* Test All Flows: Use test cards and test wallets to verify the complete payment experience
* Handle Events: Use the `useCrossmintCheckout` hook to track payment status and handle completion
## Related Resources
* [Price Limits](/payments/advanced/testing-tips#limits-in-staging)
* [Test Credit Card Numbers](/payments/advanced/testing-tips#test-credit-card-numbers)
* [Order Lifecycle](/payments/headless/guides/order-lifecycle)
* [Customizing Appearance](/payments/embedded/guides/ui-customization)
* [React Hooks](/payments/embedded/guides/hooks)
# Production Launch
Source: https://docs.crossmint.com/payments/embedded/guides/production-launch
Follow this checklist to ensure you are ready for production launch with Embedded Checkout
## From Staging to Production with Embedded Checkout
***
### Launch Checklist
1. Change your API keys to the production versions. Ensure your production API key has the appropriate scopes enabled, such as `orders.create`.
2. Update your collection ID to the production one.
3. As needed, change your passed-in props to be production ready (e.g. make `email`, `payment`, `lineItems` programmatically filled).
```jsx {(1, 4)} Production theme={null}
```
```jsx {(1, 4)} Staging theme={null}
```
# Specify Recipient
Source: https://docs.crossmint.com/payments/embedded/guides/specify-recipient
Select where purchased items should be delivered in Embedded Checkout
## Specifying the Recipient
For Embedded Checkout, you specify the recipient by adding a `recipient` object to your checkout component. The recipient can be specified by email, wallet address, or physical address for physical products.
### Recipient by Email
When specifying a recipient by email, Crossmint will automatically create a secure custodial wallet on the fly for that email address:
```jsx React {4} theme={null}
```
The recipient will be able to access their purchased items by logging into their Crossmint wallet in [staging](https://staging.crossmint.com/user/collection) or [mainnet](https://www.crossmint.com/user/collection).
### Recipient by Wallet Address
To deliver items directly to a specific blockchain wallet address:
```jsx React {4} theme={null}
```
### Physical Product Recipients
When purchasing physical products, you can specify a physical address for delivery:
```jsx React {5-13} theme={null}
```
# UI Customization
Source: https://docs.crossmint.com/payments/embedded/guides/ui-customization
Learn how to customize the Embedded Checkout UI to match your brand identity and design system
## Quick Start
The checkout UI can be customized using the `appearance` prop:
```jsx theme={null}
```
## Customization Options
### Background Customization
The embedded checkout component has a **transparent background** by default. The `backgroundPrimary` color in the appearance configuration only affects internal components (inputs, cards, etc.) within the iframe, not the iframe body itself.
To customize the overall background, wrap the component in a div with your desired background styling:
#### Solid Background Colors
```jsx theme={null}
{
/* Tailwind CSS */
}
;
{
/* Custom CSS */
}
;
```
Remember that the `backgroundPrimary` color in your appearance configuration should complement your wrapper
background for the best visual result.
### Custom Fonts
Load custom fonts using the `fonts` array:
```jsx theme={null}
appearance={{
fonts: [
{ cssSrc: "https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap" },
{ cssSrc: "https://fonts.googleapis.com/css2?family=Roboto:wght@400;500;700&display=swap" }
]
}}
```
### Global Variables
Control the overall look and feel with these global variables:
```jsx theme={null}
appearance={{
variables: {
// Typography
fontFamily: "Inter, system-ui, sans-serif",
fontSizeUnit: "16px",
// Spacing
spacingUnit: "1rem",
borderRadius: "8px",
// Colors
colors: {
borderPrimary: "#E0E0E0",
backgroundPrimary: "#FFFFFF",
textPrimary: "#000000",
textSecondary: "#666666",
danger: "#FF0000",
warning: "#FFA500",
accent: "#0074D9"
}
}
}}
```
### Component Rules
Control the visibility of specific inputs:
```jsx theme={null}
appearance={{
rules: {
DestinationInput: {
display: "hidden" // Hides the wallet address input
},
ReceiptEmailInput: {
display: "hidden" // Hides the email input
}
}
}}
```
When hiding inputs, make sure to provide the corresponding values via props:
* For hidden `DestinationInput`, provide `recipient.walletAddress`
* For hidden `ReceiptEmailInput`, provide `payment.receiptEmail`
Customize label styling:
```jsx theme={null}
appearance={{
rules: {
Label: {
font: {
family: "Inter",
size: "14px",
weight: "500",
},
colors: {
text: "#333333",
},
},
},
}}
```
Style input fields:
```jsx theme={null}
appearance={{
rules: {
Input: {
borderRadius: "8px",
font: {
family: "Inter",
size: "16px",
weight: "400",
},
colors: {
text: "#000000",
background: "#FFFFFF",
border: "#E0E0E0",
boxShadow: "none",
placeholder: "#999999",
},
hover: {
colors: {
border: "#0074D9",
},
},
focus: {
colors: {
border: "#0074D9",
boxShadow: "0 0 0 2px rgba(0,116,217,0.2)",
},
},
},
},
}}
```
Customize tab appearance:
```jsx theme={null}
appearance={{
rules: {
Tab: {
borderRadius: "4px",
font: {
family: "Inter",
size: "14px",
weight: "500",
},
colors: {
text: "#666666",
background: "transparent",
},
selected: {
colors: {
text: "#000000",
background: "#F5F5F5",
},
},
hover: {
colors: {
background: "#F0F0F0",
},
},
},
},
}}
```
Style primary buttons:
```jsx theme={null}
appearance={{
rules: {
PrimaryButton: {
borderRadius: "8px",
font: {
family: "Inter",
size: "16px",
weight: "600"
},
colors: {
text: "#FFFFFF",
background: "#0074D9"
},
hover: {
colors: {
background: "#0063B8"
}
},
disabled: {
colors: {
text: "#FFFFFF",
background: "#CCCCCC"
}
}
}
}
}}
```
## Common Examples
### Modern Dark Theme
```jsx theme={null}
appearance={{
variables: {
fontFamily: "Inter, system-ui, sans-serif",
colors: {
backgroundPrimary: "#1A1A1A",
textPrimary: "#FFFFFF",
textSecondary: "#A0A0A0",
borderPrimary: "#333333",
accent: "#7928CA"
}
},
rules: {
Input: {
colors: {
background: "#2D2D2D",
border: "#404040",
text: "#FFFFFF",
placeholder: "#666666"
}
},
PrimaryButton: {
colors: {
background: "#7928CA",
text: "#FFFFFF"
},
hover: {
colors: {
background: "#6B24B2"
}
}
}
}
}}
```
### Minimal Light Theme
```jsx theme={null}
appearance={{
variables: {
fontFamily: "system-ui, sans-serif",
borderRadius: "4px",
colors: {
backgroundPrimary: "#FFFFFF",
textPrimary: "#000000",
borderPrimary: "#E0E0E0",
accent: "#000000"
}
},
rules: {
Input: {
borderRadius: "4px",
colors: {
background: "#F5F5F5",
border: "transparent"
},
focus: {
colors: {
border: "#000000",
boxShadow: "none"
}
}
},
PrimaryButton: {
borderRadius: "4px",
colors: {
background: "#000000",
text: "#FFFFFF"
}
}
}
}}
```
## Related Resources
* [Payment Methods](/payments/embedded/guides/payment-methods)
* [React Hooks](/payments/embedded/guides/hooks)
* [Order Lifecycle](/payments/headless/guides/order-lifecycle)
* [Testing Tips](/payments/advanced/testing-tips)
# Overview
Source: https://docs.crossmint.com/payments/embedded/overview
Create seamless purchases with a fully customizable checkout experience
For an overview of our checkout solution, please refer to the [introduction](/payments/introduction). This guide specifically covers embedded checkout features.
## See It in Action
The video below showcases Embedded Checkout for memecoins on fomo, highlighting a seamless mobile onboarding experience with Apple Pay.
## When is the Embedded Checkout the best fit?
Embedded Checkout is the best choice for most teams. It offers experiences that can feel fully native (see the video above), and is a more complete product than headless. Embedded handles orchestration between multiple PSPs, error and state management, and has better approval rates than headless.
## Get Started
Start selling digital assets in 5 minutes.
Contact our sales team for advanced support.
## Advanced Topics
# Pay with Card - Memecoins (React)
Source: https://docs.crossmint.com/payments/embedded/quickstarts/credit-card-memecoin
Create a fully customized embedded memecoin checkout experience that accepts credit cards
## Introduction
This guide will show you how to accept credit card payments using Crossmint's Embedded Checkout for memecoin sales. You'll learn how to:
* Set up credit card payments for Solana memecoin purchases in JavaScript
* Implement an embedded checkout UI using Crossmint's React components
* Track order status and delivery
## Embedded Memecoin Checkout
The fastest way to start selling memecoins is to use our embedded checkout solution adapted for fungible tokens.
### Important Parameters
Before implementing the checkout, note these key parameters:
* `receiptEmail`: Optional. If the buyer pays with Apple Pay or Google Pay, Crossmint obtains the email automatically. Providing it improves fraud signal quality.
* `executionParameters.mode`: Set to "exact-in" for memecoin purchases (specifies exact USD amount to spend). "exact-out" is for NFTs, while "exact-in" is for fungible tokens.
* `maxSlippageBps`: Optional slippage tolerance (default is typically 500 BPS or 5% if not specified)
**Testing in Staging?** Use the XMEME test token: `solana:7EivYFyNfgGj8xbUymR7J4LuxUHLKRzpLaERHLvi7Dgu`. Production tokens will not work in the staging environment.
### Memecoin Embedded Integration
Next, we will set up a project file with Crossmint's embedded checkout to accept memecoin purchases.
Create `.env.local` in your project root:
```sh theme={null}
NEXT_PUBLIC_CLIENT_API_KEY="_YOUR_CLIENT_API_KEY_" # From API Keys page
NEXT_PUBLIC_TOKEN_ADDRESS="7EivYFyNfgGj8xbUymR7J4LuxUHLKRzpLaERHLvi7Dgu" # xmeme token for staging testing
NEXT_PUBLIC_RECIPIENT_WALLET_ADDRESS="YOUR_SOLANA_WALLET_ADDRESS" # Add desired recipient wallet
NEXT_PUBLIC_RECEIPT_EMAIL="YOUR_EMAIL" # Add desired recipient email
```
Create `/src/app/page.tsx` with:
```tsx theme={null}
"use client";
import { CrossmintProvider, CrossmintEmbeddedCheckout } from "@crossmint/client-sdk-react-ui";
export default function Home() {
const clientApiKey = process.env.NEXT_PUBLIC_CLIENT_API_KEY as string;
const tokenAddress = process.env.NEXT_PUBLIC_TOKEN_ADDRESS as string;
const recipientWalletAddress = process.env.NEXT_PUBLIC_RECIPIENT_WALLET_ADDRESS as string;
return (
);
}
```
For more details on tokenLocator formatting and other item selection options, see our [embedded item selection guide](/payments/embedded/guides/item-selection#item-selection-examples).
Test purchases in staging using test credit cards, like `4242424242424242` (Visa). More information on testing can be found [here](/payments/advanced/testing-tips#test-credit-card-numbers).
For production launch with live memecoin tokens, contact the Crossmint sales team — production Checkout requires a signed Order Form and successful KYB. See the [production launch guide](/payments/embedded/guides/production-launch) for the full sequence.
Here's how your embedded checkout will look after implementation:
Note the quote expiration timer above the checkout button.
🎉 Congratulations! You've successfully set up your embedded memecoin checkout. Check out the [Next Steps](#next-steps) section below to learn how to customize your integration.
## Next Steps
Learn how to customize the embedded checkout experience
Implement webhook handling for order updates
If the quote expires (after 1 minute), the embedded checkout will automatically refresh the quote. You can
customize this behavior using the checkout hooks.
# Pay with Card - Memecoins (Flutter)
Source: https://docs.crossmint.com/payments/embedded/quickstarts/credit-card-memecoin-flutter
Create a fully customized embedded memecoin checkout experience for Flutter that accepts credit cards
For this quickstart, you will create a minimal Flutter app. Make sure you have Flutter installed.
## Create a New Flutter Project + Crossmint SDK
```bash theme={null}
flutter create crossmint_checkout_quickstart
cd crossmint_checkout_quickstart
```
Pass your API key and configuration via Dart defines:
```bash theme={null}
flutter run \
--dart-define=CROSSMINT_API_KEY=YOUR_CLIENT_SIDE_API_KEY \
--dart-define=RECIPIENT_WALLET=YOUR_SOLANA_WALLET_ADDRESS \
--dart-define=RECEIPT_EMAIL=YOUR_EMAIL@example.com
```
The `receiptEmail` field is **optional**. If the buyer pays with Apple Pay or Google Pay, Crossmint obtains the email automatically. Providing it improves fraud signal quality.
```dart theme={null}
import 'package:flutter/material.dart';
import 'package:crossmint_flutter/crossmint_flutter_ui.dart';
const apiKey = String.fromEnvironment('CROSSMINT_API_KEY');
const recipientWallet = String.fromEnvironment('RECIPIENT_WALLET');
const receiptEmail = String.fromEnvironment('RECEIPT_EMAIL');
void main() {
runApp(const MaterialApp(home: CheckoutScreen()));
}
class CheckoutScreen extends StatelessWidget {
const CheckoutScreen({super.key});
@override
Widget build(BuildContext context) {
final checkoutController = CrossmintCheckoutController();
return Scaffold(
body: SafeArea(
child: CrossmintEmbeddedCheckout(
apiKey: apiKey,
config: CrossmintCheckoutConfig(
order: CrossmintCheckoutNewOrder.typed(
lineItems: [
CrossmintCheckoutLineItem.tokenWithExecutionParameters(
tokenLocator:
'solana:7EivYFyNfgGj8xbUymR7J4LuxUHLKRzpLaERHLvi7Dgu', // XMEME token (staging)
executionParameters: {
'mode': 'exact-in',
'amount': '1',
'maxSlippageBps': '500',
},
),
],
recipient: CrossmintCheckoutWalletRecipient(
walletAddress: recipientWallet,
),
),
payment: CrossmintCheckoutPayment(
fiat: CrossmintCheckoutFiatPayment(enabled: true),
crypto: CrossmintCheckoutCryptoPayment(enabled: false),
receiptEmail: receiptEmail,
),
),
checkoutController: checkoutController,
onOrderUpdated: (order) =>
debugPrint('Order updated: ${order['status']}'),
onOrderCreationFailed: (error) =>
debugPrint('Error: $error'),
),
),
);
}
}
```
Run with the same `--dart-define` flags from step 3:
```bash iOS theme={null}
flutter run -d ios \
--dart-define=CROSSMINT_API_KEY=YOUR_CLIENT_SIDE_API_KEY \
--dart-define=RECIPIENT_WALLET=YOUR_SOLANA_WALLET_ADDRESS \
--dart-define=RECEIPT_EMAIL=YOUR_EMAIL@example.com
```
```bash Android theme={null}
flutter run -d android \
--dart-define=CROSSMINT_API_KEY=YOUR_CLIENT_SIDE_API_KEY \
--dart-define=RECIPIENT_WALLET=YOUR_SOLANA_WALLET_ADDRESS \
--dart-define=RECEIPT_EMAIL=YOUR_EMAIL@example.com
```
```bash Chrome theme={null}
flutter run -d chrome \
--dart-define=CROSSMINT_API_KEY=YOUR_CLIENT_SIDE_API_KEY \
--dart-define=RECIPIENT_WALLET=YOUR_SOLANA_WALLET_ADDRESS \
--dart-define=RECEIPT_EMAIL=YOUR_EMAIL@example.com
```
# Pay with Card - Memecoins (React Native)
Source: https://docs.crossmint.com/payments/embedded/quickstarts/credit-card-memecoin-react-native
Create a fully customized embedded memecoin checkout experience for React Native that accepts credit cards
# Crossmint Checkout React Native SDK Quickstart
For this quickstart we'll be using Expo. You can follow this [tutorial](https://docs.expo.dev/tutorial/create-your-first-app/) to create an expo app. **Only do steps 1 and 3**, plus the Crossmint-specific steps below.
**Prerequisites:** Before starting, ensure you have completed the [Expo prerequisites](https://docs.expo.dev/tutorial/create-your-first-app/#prerequisites) including Node.js, Git, and your development environment setup.
## Following Expo Tutorial + Crossmint SDK
```bash theme={null}
npx create-expo-app@latest crossmint-checkout-quickstart
cd crossmint-checkout-quickstart
```
```bash theme={null}
npm run reset-project
```
```bash theme={null}
EXPO_PUBLIC_CLIENT_CROSSMINT_API_KEY="your_client_side_api_key"
EXPO_PUBLIC_RECIPIENT_WALLET_ADDRESS="your_solana_wallet_address"
EXPO_PUBLIC_RECEIPT_EMAIL="your-email@example.com"
```
The `receiptEmail` field is **optional**. If the buyer pays with Apple Pay or Google Pay, Crossmint obtains the email automatically. Providing it improves fraud signal quality.
```jsx theme={null}
import { CrossmintEmbeddedCheckout, CrossmintProvider } from "@crossmint/client-sdk-react-native-ui";
export default function Index() {
return (
);
}
```
```bash npm theme={null}
npm run ios
# For Android:
npm run android
```
```bash yarn theme={null}
yarn ios
# For Android:
yarn android
```
```bash pnpm theme={null}
pnpm ios
# For Android:
pnpm android
```
```bash bun theme={null}
bun ios
# For Android:
bun android
```
### Result
# Pay with Card - NFTs (React)
Source: https://docs.crossmint.com/payments/embedded/quickstarts/credit-card-nft
Embed a onchain checkout into a web app in under 5 minutes
## Introduction
In this guide, you will create a web app with Next.js which allows customers to buy NFTs with credit card and crypto payments, using Crossmint's embedded checkout. Crossmint also supports payments for [crypto onramp](/onramp/quickstarts/react), [memecoins](/payments/headless/quickstarts/credit-card-memecoin-cko), and other onchain assets (ERC20 tokens, ERC721 tokens, and ERC1155 tokens).
If you want to get started immediately, you can clone a repo with a functioning embedded checkout here: [https://github.com/Crossmint/crossmint-embedded-demo](https://github.com/Crossmint/crossmint-embedded-demo). If you want to get started step by step, continue following the guide below.
### Basic Integration
Perfect for getting started quickly with a simple checkout flow.
Create `.env.local` in your project root:
```sh theme={null}
NEXT_PUBLIC_CLIENT_API_KEY="_YOUR_CLIENT_API_KEY_" # From API Keys page
NEXT_PUBLIC_COLLECTION_ID="_YOUR_COLLECTION_ID_" # From Collection details page
```
Replace your home page (e.g `{root_dir}/app/page.tsx`) with:
```tsx theme={null}
"use client";
import { CrossmintProvider, CrossmintEmbeddedCheckout } from "@crossmint/client-sdk-react-ui";
export default function Home() {
const clientApiKey = process.env.NEXT_PUBLIC_CLIENT_API_KEY as string;
const collectionId = process.env.NEXT_PUBLIC_COLLECTION_ID as string;
return (
);
}
```
Navigate to the directory your package.json is to run the app
```bash theme={null}
cd embedded-checkout/crossmint-embedded-checkout-demo
```
### Advanced Integration
Need purchase tracking, multiple NFT purchases at once, or more customization? Here's a complete setup with additional features:
```tsx theme={null}
"use client";
import { useEffect } from "react";
import {
CrossmintProvider,
CrossmintCheckoutProvider,
CrossmintEmbeddedCheckout,
useCrossmintCheckout,
} from "@crossmint/client-sdk-react-ui";
// Component with purchase tracking
function Checkout() {
const { order } = useCrossmintCheckout();
const collectionId = process.env.NEXT_PUBLIC_COLLECTION_ID as string;
useEffect(() => {
if (order && order.phase === "completed") {
console.log("Purchase completed!");
// Handle successful purchase
}
}, [order]);
return (
);
}
// Main component with providers
export default function Home() {
const clientApiKey = process.env.NEXT_PUBLIC_CLIENT_API_KEY as string;
return (
);
}
```
This advanced example showcases: - Multiple NFTs in one checkout - Purchase status tracking - Preferred payment
methods and currencies - Email-based NFT delivery - Language settings Learn more in our guides: - [Payment
Methods](/payments/embedded/guides/payment-methods) - [Multi-purchases](/payments/pay-button/guides/item-selection#multiple-item-orders) -
[React Hooks](/payments/embedded/guides/hooks)
### Testing Your Integration
This demo uses the staging environment: - Use [test credit
cards](/payments/advanced/testing-tips#test-credit-card-numbers) for payments - Get test USDC from our
[faucet](/payments/advanced/testing-tips#getting-test-tokens) - Check [price
limits](/payments/advanced/testing-tips#limits-in-staging) for staging
## All Set!
You've successfully integrated card and crypto payments with the Crossmint Embedded Checkout!
Remember this demo is built on staging, so the digital assets will show up on the testnets. To launch on production,
check the [production launch checklist](/payments/advanced/production-launch). You will need to contact
[Sales](https://www.crossmint.com/contact/sales) to enable the embedded checkout on production.
## Next Steps
### Customize the UI and Behavior Further
Learn how to customize the look and feel of your checkout
Configure available payment options for your users
Use React hooks to build custom checkout experiences
### Advanced Topics
# Create/Deploy an NFT Collection
Source: https://docs.crossmint.com/payments/guides/create-collection
Easily deploy a collection in the console and enable payments
The easiest way to get started with selling or airdropping NFTs is to use the developer console to deploy and manage your NFT contracts.
## Create a Crossmint NFT Collection
This guide will deploy an ERC-721 contract on the Polygon Amoy testnet. Currently, you can also deploy to the mainnets and testsnets for Polygon, Base, Optimism, and Solana directly from the console. Additional chains are supported when deploying via the [create-collection API](/api-reference/minting/collection/create-collection).
This information is displayed in the Hosted Checkout and Storefront. You can edit it later.
If you want to use this collection exclusively with the [Minting Tools](/minting/introduction), select the `Airdrop NFTs` option only.
Crossmint has staging and production [environments](/introduction/platform/staging-vs-production) to facilitate working in testnets or mainnets.
When creating a new collection on production you'll need some API credits to cover the gas cost of contract deployment.
#### NFT Price
Enter the price you want to charge for your NFT. When using the staging environment, please set very low test prices to help use preserve our testnet currency. Everything works the same in staging and production, so you can test your collection with low prices in staging and then deploy to production with higher prices.
In production, there is a lower bound limit of USD $0.75, and an upper bound limit of USD $1,500 per transaction. You can find more information if you need to [increase this limit](/payments/advanced/production-launch#default-transaction-limit).
#### USDC
Several chains support USDC for the currency. This option is useful for price stability and to receive payouts in a stable token. You can find more info on [configuring USDC here](/payments/advanced/usdc-support).
### Fee Sponsorship
This option enables you to control who pays the fees of the NFT purchase including gas and credit card transaction fees. The default option is "Buyer", which means that if you set a price of 10 USDC, the final price the buyer pays will be 10 USDC plus fees.
If you want to provide a consistent price for your customers, select the "You" option to sponsor the fees. When you sponsor the fees they will be deducted from the amount the buyer pays and the remainder will be paid to the recipient wallet you set in the next step.
#### Recipient Address
Enter the wallet address where you want to receive payments. This is the address where Crossmint will send the proceeds from NFT sales.
Want to split payments across multiple addresses? Use a [splits.org](https://splits.org) address as the payout recipient.
Make sure you review the Content policy. When launching in production you'll need to submit collection verification information to ensure your collection is compliant with our Content Policy. You can find more information about everything required for your production launch in the [Production Launch](/payments/advanced/production-launch) section.
## Adding NFTs
This is **required** before you can sell or airdrop the NFTs. Without this step there is not any actual information to use for creating the NFTs yet. You can create unique NFT metadata per token or an Open Edition style NFT where all tokens share the same metadata.
#### NFT Name
The name of your NFT (max 32 characters).
#### Supply
For unique per NFT, enter 1. You will need to configure the metadata for each NFT. You can do this via the developer console individually. For projects with many unique NFTs you should use the [Create Template API](/api-reference/minting/template/create-template) with a script.
For an Open Edition NFT, enter the total quantity, or select the Unlimited checkbox. This will be the only metadata you need to configure.
#### Description
The description of your NFT (max 64 characters).
#### Image
Upload an image for your NFT.
Recommended image formats are: JPEG, PNG, WEBP, or GIF. In most cases, the medium in which NFTs are displayed don't require extremely high resolution files. Strike a balance and lean towards file sizes that do not require significant bandwidth to download (below 10 MB for example).
#### Attributes `optional`
Additional attributes of the NFT. Add as few or as many as you like. You can refer to the [OpenSea metadata standards](https://docs.opensea.io/docs/metadata-standards#attributes) page for detailed explanations of how to use these in your project.
Click the `Create NFT` button to complete.
You can download an example of how your file should be structured here: [batch-upload-example.zip](https://www.crossmint.com/assets/examples/batch-upload-example.zip).
You can upload a maximum of 1000 items at a time. For collections that require more than 1000 tokens, you can repeat the batch upload process multiple times.
The CSV should include a header row with the following fields:
* `name` - The name for the token.
* `image` - The filename of the image, which will be uploaded in the next step.
* `description` - A description for the token.
* `supply ` - How many of the token should be available.
* `animation_url` - (optional)
You can add attributes via additional columns in the spreadsheet. For example, to add an attribute named `weapon`, include an additional column with the attribute name for the header row. You can refer to the example file linked above to see this in action.
Once you have your CSV ready, upload it to move on to the next step for media files.
The image file names must match the value(s) in the `image` column of the uploaded CSV file. These files should be in a flat folder structure for upload (no sub-folders).
Click the "Upload media files" button and then select all of the images referenced in the `metadata.csv` file you uploaded in the previous step.
Click the "Upload" button to upload the CSV and media files. If any media files are missing you'll be notified which ones they are in the UI.
If you need to upload additional token metadata click the "New batch upload" button. Otherwise select "View my NFTS" to close the modal and view the metadata for your tokens.
You can also perform bulk uploading by calling the [create-template](/api-reference/minting/template/create-template) API using a script that loops through your `metadata.csv` file. You'll need to have an [API key](/introduction/platform/api-keys#how-to-get-and-use-api-keys) with the `nfts.create` scope enabled.
Below is an example script that will keep your request volume below the 120/min [rate limit](/introduction/platform/api-keys#rate-limits).
This code example is available as a repository on GitHub also:
[crossmint/bulk-uploader](https://github.com/Crossmint/bulk-uploader)
Before running this script you'll need to upload your media files to a service that can host the files, such as [Pinata](https://www.pinata.cloud/).
The `image` column of your `metadata.csv` should include the full URL to the file instead of only a file name like the batch upload option above.
```javascript bulkUploader.js theme={null}
const fs = require("fs");
const csv = require("csv-parser");
require("dotenv").config();
const collectionId = process.env.COLLECTION_ID;
const apiKey = process.env.API_KEY;
const apiUrl = `https://staging.crossmint.com/api/2022-06-09/collections/${collectionId}/templates`;
// Rate limiting setup
const rateLimit = 100; // requests per minute
const interval = 60000 / rateLimit; // interval in milliseconds
async function sendRequest(data) {
try {
const response = await fetch(apiUrl, {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-API-KEY": `${apiKey}`,
},
body: JSON.stringify(data),
});
const responseData = await response.json();
console.log("Success:", responseData);
} catch (error) {
console.error("Error:", error.message);
}
}
// Read and process the CSV file
function processFile() {
let promise = Promise.resolve();
fs.createReadStream("metadata.csv")
.pipe(csv())
.on("data", (row) => {
promise = promise.then(() => {
const postData = {
metadata: {
name: row.name,
image: row.image, // this should be a publicly accessible URL
description: row.description,
},
supply: { limit: Number(row.supply) },
reuploadLinkedFiles: false, // this is optional
};
return sendRequest(postData).then(() => new Promise((resolve) => setTimeout(resolve, interval)));
});
})
.on("end", () => {
promise.then(() => {
console.log("Finished processing file.");
});
});
}
processFile();
```
Run the script to bulk upload your template metadata.
```node theme={null}
node bulkUploader.js
```
## Send a Test NFT
Once you have added metadata you can send a test NFT to yourself or another user. The interface to mint and send from the console supports email and wallet address for the recipient.
You can mint and send a test NFT to an email address or wallet to make sure everything is working as expected.
If you enter an email address the NFT will be sent to a unique Crossmint custodial wallet associated with that email address. The owner of this email can log in to Crossmint to view their NFT. The wallet option will send directly to the wallet address.
Crossmint does ***not*** send an email to the user when you airdrop NFTs from the console.
That's all there is to it. Check below for recommended next steps.
# Register External Collection
Source: https://docs.crossmint.com/payments/guides/register-collection
Import custom and/or third party contracts
Crossmint has a pre-audited library of [smart contracts](/minting/nfts/integrate/create-collections) that serve most use cases. However, if you have custom needs, you can also bring your own.
The Checkout has been battle-tested at scale with Crossmint's collections, and may result in a more reliable
experience than using an untested, imported contract, or at least require less trouble-shooting.
* When you require very custom functionality not supported by Crossmint's contracts.
* If you're developing a marketplace.
* If you have an accesslist.
Secondary markets are platforms where NFTs are traded after their initial mint/primary sale. These are marketplaces where users can buy and sell NFTs from each other.
It requires manual support from Crossmint to import secondary contracts. See our [marketplaces and launchpads guide](/payments/advanced/marketplaces-and-launchpads).
The self-serve option is only available for importing primary contracts for EVM and Solana.
When importing contracts, Crossmint has support to fetch listings from the following secondary marketplaces:
* **Auction House**: Solana's native marketplace protocol
* **Tensor**: High-performance Solana NFT marketplace
* **Magic Eden API**: Popular multi-chain NFT marketplace
* **Hyperspace**: Cross-chain NFT marketplace and aggregator
To add support for a new marketplace, please [contact sales](https://www.crossmint.com/contact/sales?utm_source=devrel\&utm_medium=docs\&utm_campaign=backlinks).
## Quick Navigation
All collection imports start with the same initial steps, then diverge based on your blockchain:
* [Common Steps (All Chains)](#common-steps-all-chains) - Steps 1-4 for all blockchains
* [EVM-Specific Steps](#evm-specific-steps) - Steps for Ethereum, Polygon, etc.
* [Solana-Specific Steps](#solana-specific-steps) - Additional steps for Solana candy machines
## Common Steps (All Chains)
These initial steps are the same whether you're importing an EVM contract or a Solana candy machine:
This information is displayed in the Hosted Checkout and Storefront. You can edit it later.
Remember, Crossmint has staging and production [environments](/introduction/platform/staging-vs-production).
When working in the staging environment, choose the mainnet name. For example, if your contract is on the
Polygon Amoy testnet, select Polygon.
After completing these common steps, continue with the blockchain-specific instructions below.
## EVM-Specific Steps
Complete the [Common Steps](#common-steps-all-chains) above first, then continue with these EVM-specific steps:
### Pre-requisites
* Your contract must be ERC-721, ERC-721A, or ERC-1155 compliant.
* The minting function must allow minting directly to an address that is different from the one that invoked the contract. And it must contain at least one parameter that specifies that recipient address.
* A single address must be able to call the mint function unlimited times but does not need to be able to hold unlimited NFTs.
### Contract Registration
You may register contracts manually from the console or via API.
When you compile your smart contract there will be a corresponding abi file with an `.abi` or `.json` extension.
Inside this file, you'll see JSON property named abi, which describes the functions in your smart contract. Here's an example of a very simple abi file. Yours will likely have more function descriptions.
```json theme={null}
// Example generated abi file for smart contract
{
"abi": [
{
"inputs": [],
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"inputs": [
{
"internalType": "address",
"name": "_to",
"type": "address"
}
],
"name": "mintTo",
"outputs": [],
"stateMutability": "payable",
"type": "function"
}
]
}
```
Copy the JSON array object that comes after the string `abi` and paste it into the `Contract ABI` text box in the developer console. The content you paste in should begin with `[` and end with `]`.
Whether your ABI was retrieved automatically or you pasted it in manually, you need to specify the:
* Mint function
* Recipient address parameter name
* \[Optional] Quantity of NFTs to mint parameter name
Crossmint will attempt to automatically select these values for you, but it's important to ensure they are set correctly. Especially if you're setting up a USDC mint function as the list of options will be longer.
Proxy contracts are an advanced feature. You should use this only if you are certain that your contracts adhere to this pattern. This is crucial because Crossmint requires the actual NFT contract address when you register a mint/buy/purchase/claim function in a sales contract or revenue splitter.
If you don't specify the NFT contract address, our system won't be able to extract token URI information or facilitate transfers. Set this up only if it isn't a transparent proxy, which is common for upgradeable contracts.
### EVM Contract Import Steps
Enter the address of your contract and the console will determine the contract type automatically.
Notice in the screenshot that the example has the same contract address that was entered in previous step. This indicates a basic setup where the purchase function is within the registered NFT contract. The only reason to change this is if purchases should be sent to a different contract address that is not the same as the NFT contract.
Most developers do not need to change this. If you are unsure, leave it as is. Transparent upgradeable contracts also do not need to use this setting and should use the beacon address in the previous step.
As long as your contract is verified on the block explorer we can query the ABI automatically. If you have not verified your contract, you must enter the ABI manually.
Unless you specifically deployed a contract that supports USDC you must leave the native currency selected. If you are unsure, leave it as is.
If you selected the native currency in the previous step, this will be a very short list. If you have multiple mint functions in your contract that you want to accept payments for, you must register each one separately.
USDC mint functions typically are NOT `payable`. Change the currency selector to USDC to populate the mint function list with valid options.
The mint function must accept an address parameter and mint to that address. If your mint function lacks this you'll need to deploy a new contract or modify your existing contract.
After the mint function has been selected, this will be a short list built from the function parameters of type `address`. If you have multiple options here, ensure that you select the correct one. Doing this incorrectly will result in the purchased NFTs being sent to the wrong address and they likely will not be recoverable.
This will be a short list built from the function parameters of type `uint256`. If you have multiple options here, ensure that you select the correct one. Doing this incorrectly will result in the NFT checkout tools not being able to properly mint the correct quantity of NFTs.
## Solana-Specific Steps
Complete the [Common Steps](#common-steps-all-chains) above first, then continue with these Solana-specific steps:
Instant support for:
* **Primaries**: Candy Machine v3, Metaplex Instant Sales, Magic Eden's Launchpad
* **Secondaries**: Auction House, Tensor, Magic Eden API, Hyperspace
Only the integrations listed above are available at the moment. For custom integrations, please [contact
sales](https://www.crossmint.com/contact/sales?utm_source=devrel\&utm_medium=docs\&utm_campaign=backlinks).
### Solana Candy Machine Import
Crossmint supports Candy Machine v3. After completing the common steps above, continue with these Solana-specific steps:
This step replaces step 4 from the common steps above - select Solana from the blockchain dropdown.
Find the ID in the `cache.json` file in the folder where you deployed your Candy Machine.
Congratulations! 🎉 You've successfully registered your external collection in Crossmint.
## FAQs
For Xion blockchain contracts, please refer to our [Xion Contract Requirements](/minting/advanced/xion-contracts) documentation.
Currently only importing EVM and Solana primary contracts is self-serve. If you wish to import Aptos and Sui contracts, please [contact
sales](https://www.crossmint.com/contact/sales?utm_source=devrel\&utm_medium=docs\&utm_campaign=backlinks).
Yes! You can programmatically register collections using the Collections Registration API.
**API Endpoint:** `POST https://staging.crossmint.com/api/v1-alpha1/collections`
**Required Headers:**
* `Content-Type: application/json`
* `x-api-key: ` (requires `collections.create` scope)
```bash EVM Contract Example theme={null}
curl --request POST \
--url https://staging.crossmint.com/api/v1-alpha1/collections \
--header 'content-type: application/json' \
--header 'x-api-key: ' \
--data '{
"chain": "ethereum",
"contractType": "erc-721",
"args": {
"contractAddress": "0x1234567890123456789012345678901234567890",
"abi": "[{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_to\",\"type\":\"address\"}],\"name\":\"mintTo\",\"outputs\":[],\"stateMutability\":\"payable\",\"type\":\"function\"}]",
"mintFunctionName": "mintTo(address)",
"toParamName": "_to",
"quantityParamName": "_quantity",
"contractType": "erc-721"
},
"metadata": {
"title": "My NFT Collection",
"description": "A custom NFT collection",
"imageUrl": "https://example.com/collection-image.png",
"social": {
"twitter": "https://twitter.com/mycollection",
"discord": "https://discord.gg/mycollection"
}
},
"ownership": "external",
"category": "art",
"scopes": ["payments:credit-card", "payments:cross-chain"]
}'
```
```bash Solana Candy Machine Example theme={null}
curl --request POST \
--url https://staging.crossmint.com/api/v1-alpha1/collections \
--header 'content-type: application/json' \
--header 'x-api-key: ' \
--data '{
"chain": "solana",
"contractType": "candy-machine",
"args": {
"candyMachineId": "your-candy-machine-id-here"
},
"metadata": {
"title": "My Solana Collection",
"description": "A Solana NFT collection",
"imageUrl": "https://example.com/collection-image.png"
},
"ownership": "external",
"category": "art",
"scopes": ["payments:credit-card", "payments:cross-chain"]
}'
```
**Key Parameters:**
* **chain**: `"solana"`, `"ethereum"`, `"polygon"`, `"bsc"`
* **contractType**: `"candy-machine"`, `"erc-721"`, `"erc-1155"`, `"thirdweb-drop"`
* **args**: Contract-specific arguments (see examples above)
* For EVM: `contractAddress`, `abi`, `mintFunctionName`, `toParamName`, `contractType` (required); `quantityParamName`, `tokenContractAddress` (optional)
In EVM, if you use a separate contract for minting than for token metadata, set the minting contract address as `contractAddress`, and the token contract address as `tokenContractAddress`.
* For Solana: `candyMachineId` (required)
* **metadata**: Object with `title`, `description`, `imageUrl` (all required), and optional `social` object with `twitter`/`discord` links
* **ownership**: `["self", "external"]`. Set to `"external"` for imported contracts
* **category**: `"art"`, `"gaming"`, `"music"`, `"loyalty"`, `"ticketing"`, `"charity"`, `"other"`
* **scopes**: `["payments:credit-card", "payments:cross-chain"]`. Scopes are required when `ownership` is `"external"`
**Response (201 Created):**
The API returns a collection object with identifiers you can use with Crossmint's checkout and payment systems.
```json theme={null}
{
"collectionId": "abc123-def456-ghi789...",
"clientId": "abc123-def456-ghi789..."
}
```
This API is currently in alpha (v1-alpha1) and subject to change. The returned `collectionId` and `clientId` are typically the same value and can be used interchangeably for checkout integration.
# Client or Server
Source: https://docs.crossmint.com/payments/headless/guides/client-or-server
Understand the reasons to use client-side or server-side API keys
The headless checkout supports both server-side and client-side API keys. It's important that you select the right key for your implementation.
## When to use a server-side API key
* Testing in the [API Playground](/api-reference/headless/create-order) of the documentation
* Testing with cURL requests or running scripts from your command line
* Building applications that make API calls to your own backend, which then make the actual API call to Crossmint
The key consideration here is if the API request is coming from a server environment.
### Server Side Example Code
```typescript route.ts (server-side) theme={null}
import { callCrossmintAPI } from "@/app/utils/crossmint";
import { NextRequest, NextResponse } from "next/server";
const crossmintBaseUrl = process.env.CROSSMINT_API_URL;
const crossmintApiKey = process.env.CROSSMINT_API_KEY;
const crossmintAPIHeaders = {
accept: "application/json",
"content-type": "application/json",
"x-api-key": crossmintApiKey,
};
const callCrossmintAPI = async (endpoint: string, options: { method: string; body?: any; params?: any }) => {
const url = `${crossmintBaseUrl}/${endpoint}`;
const { body, method } = options;
const response = await fetch(url, {
body: body ? JSON.stringify(body) : null,
method,
headers: crossmintAPIHeaders,
});
const json = await response.json();
return json;
};
export async function POST(req: NextRequest, res: NextResponse) {
try {
const body = await req.json();
console.log("create order: ", body);
const apiResponse = await callCrossmintAPI("/orders", {
method: "POST",
body,
});
return NextResponse.json(apiResponse, { status: 200 });
} catch (error) {
console.log("failed to create order");
return NextResponse.json({ message: "Error creating order" }, { status: 500 });
}
}
```
## When to use a client-side API key
* Your application will be making API requests to Crossmint directly from a browser
When you create client-side API keys you must add the authorized origins that can use the key. For example, in
testing you'll need to indicate `http://localhost:3000` (or similar local dev URLs) as authorized origins, or the
request will be denied.
There is one additional step when using a client-side API key in your application with headless checkout. The first call will be to create the order. The response will include a `clientSecret` property that you must persist in state and then pass as an additional header in subsequent API requests to the [update-order](/api-reference/headless/edit-order) or [get-order](/api-reference/headless/get-order) routes.
### Client Side Example Code
The sample code below shows direct client-side API calls to Crossmint using a client-side API key. The `component.tsx` file is simplified to only show the relevant logic for making requests directly from the browser to Crossmint's API.
```tsx component.tsx (create-order) theme={null}
// note the end of try block where the clientSecret is saved to local state
const { setOrder, setClientSecret } = useLocalState();
const createOrder = async (orderInput: any) => {
try {
const res = await fetch(`https://staging.crossmint.com/api/2022-06-09/orders`, {
method: "POST",
headers: {
"Content-Type": "application/json",
"x-api-key": process.env.NEXT_PUBLIC_CLIENT_SIDE_KEY,
},
body: JSON.stringify(orderInput),
});
const order = await res.json();
setOrder(order.order);
setClientSecret(order.clientSecret);
} catch (e) {
console.error(e);
throw new Error("Failed to create order");
}
};
```
```tsx component.tsx (update-order) theme={null}
// note the `authorization` header, which contains previously saved clientSecret
const { order, clientSecret, setOrder } = useLocalState();
const updateOrder = async (orderInput: any) => {
try {
const res = await fetch(`https://staging.crossmint.com/api/2022-06-09/orders/${order.orderId}`, {
method: "PATCH",
headers: {
"Content-Type": "application/json",
"x-api-key": process.env.NEXT_PUBLIC_CLIENT_SIDE_KEY,
authorization: clientSecret,
},
body: JSON.stringify(orderInput),
});
const updatedOrder = await res.json();
setOrder(updatedOrder);
} catch (e) {
console.error(e);
throw new Error("Failed to update order");
}
};
```
```tsx component.tsx (get-order) theme={null}
// note the `authorization` header, which contains previously saved clientSecret
const { order, clientSecret, setOrder } = useLocalState();
const getOrder = async () => {
try {
const res = await fetch(`https://staging.crossmint.com/api/2022-06-09/orders/${order.orderId}`, {
method: "GET",
headers: {
"Content-Type": "application/json",
"x-api-key": process.env.NEXT_PUBLIC_CLIENT_SIDE_KEY,
authorization: clientSecret,
},
});
const refreshedOrder = await res.json();
setOrder(refreshedOrder);
return refreshedOrder.lineItems[0].delivery.status;
} catch (e) {
console.error(e);
throw new Error("Failed to fetch order");
}
};
```
### Mobile Applications
When using client-side API keys in mobile applications, you must include an additional `x-app-identifier` header in your requests. This header should contain the bundle identifier (iOS) or package name (Android) that you whitelisted when creating your API key.
```swift iOS (Swift) theme={null}
let headers = [
"Content-Type": "application/json",
"x-api-key": "your-client-side-api-key",
"x-app-identifier": "com.yourcompany.yourapp", // iOS Bundle ID
"authorization": clientSecret // When updating/reading orders
]
```
```kotlin Android (Kotlin) theme={null}
val headers = mapOf(
"Content-Type" to "application/json",
"x-api-key" to "your-client-side-api-key",
"x-app-identifier" to "com.yourcompany.yourapp", // Android Package Name
"authorization" to clientSecret // When updating/reading orders
)
```
```javascript React Native theme={null}
const headers = {
"Content-Type": "application/json",
"x-api-key": "your-client-side-api-key",
"x-app-identifier": "com.yourcompany.yourapp", // Bundle ID or Package Name
authorization: clientSecret, // When updating/reading orders
};
```
The `x-app-identifier` must match one of the mobile app identifiers you whitelisted when creating your client-side
API key. For more information on setting up mobile app identifiers, see the [Client-side API
Keys](/introduction/platform/api-keys/client-side#mobile-app-identifiers) documentation.
# Design Your UI
Source: https://docs.crossmint.com/payments/headless/guides/design-your-ui
How to get started with setting up your UI
This guide will walk you through the basic steps of implementing a user interface integrated with the Headless Checkout. The first step in the process will be to create an Order via API call.
You can create an order on load and add the recipient later, or wait to create the order when you have the recipient info ready. Creating the order immediately helps obtain the price quote for display. Let's proceed with that approach in mind.
## Building Your App
There are four key components you'll need to represent in the UI for your users.
1. Purchase Preview - a visual explanation of what they are buying and the price
2. Wallet Connection - cross-chain payments will require the ability to connect a wallet
3. Payment Status - keeping the user updated about the payment acceptance
4. Delivery Status - progress of delivery and information upon completion/failure
### Purchase Preview
It is common to create an order upon initial render for your user, and then update the details of that order as the adjustments are made by the user. You'll need to create a function in your application that can call the create-order API and save the response.
After you create the `order` you'll get back metadata related to the `collectionId` you pass in the [create-order](/api-reference/headless/create-order) call. You can use this metadata to build the preview highlighted in the screenshot below.
```jsx theme={null}
useEffect(() => {
if (order != null) {
return;
}
createOrder({
payment: {
method: "ethereum-sepolia",
currency: "eth",
},
locale: "en-US",
lineItems: [
{
collectionLocator: "crossmint:YOUR_CROSSMINT_COLLECTION_ID",
callData: {
totalPrice: "0.001",
},
},
],
});
}, [order]);
```
### Wallet Connection
To enable the cross-chain payments feature, you'll need to provide an easy way for users to connect their wallet. This guide will not go into detail about how to add any specific tools, since there are a variety available and the implementation of each is unique to the tool.
However, you will want to set up your app to listen for changes to the primary connected wallet and selected network so that the order is updated automatically.
Once the user has connected their wallet they might still change the primary connected account or network. You need to listen for these changes and update the order.
Note the dependency array at the end of this `useEffect` watches `primaryWallet` and `selectedNetwork`. If any of these change your app needs to refresh the order with the new values.
```jsx theme={null}
useEffect(() => {
if (order == null || primaryWallet == null) {
return;
}
const currentRecipient = order.lineItems[0].delivery.recipient?.walletAddress;
const isSameWallet = currentRecipient === primaryWallet.address;
const isSameNetwork = order.payment.method === selectedNetwork;
if (isSameWallet && isSameNetwork) {
return;
}
updateOrder({
recipient: {
walletAddress: primaryWallet.address,
},
payment: {
method: selectedNetwork,
currency: "eth",
payerAddress: primaryWallet.address,
},
});
}, [primaryWallet, selectedNetwork]);
```
# Item Selection
Source: https://docs.crossmint.com/payments/headless/guides/item-selection
Specify which items to purchase with Headless Checkout API
The Headless Checkout API allows you to specify which items your users can purchase using the `lineItems` property. This guide explains how to configure item selection for different asset types.
## JSON Item Selection Examples
```json collectionLocator theme={null}
{
"lineItems": [
{
"collectionLocator": "crossmint:_YOUR_COLLECTION_ID_", // Crossmint managed collection
// "collectionLocator": "crossmint:_YOUR_COLLECTION_ID_:_TEMPLATE_ID_", // With specific template
"callData": {
"totalPrice": "5.00",
"quantity": 1 // matches your contract's parameter name
}
}
]
}
// To use exact-in mode, add executionParameters: { maxSlippageBps: "500" } to the lineItem object. For custom contract params, add executionParameters: { customParam: "value" }.
```
```json collectionLocator - imported contract theme={null}
{
"lineItems": [
{
"collectionLocator": "ethereum:0x71c7656ec7ab88b098defb751b7401b5f6d897", // External collection registered in Crossmint Console
"callData": {
"totalPrice": "5.00",
"quantity": 1 // matches your contract's parameter name
// For external contracts, you might need additional parameters such as:
// "_amount": 1, // if your mint function uses _amount instead of quantity
// "tokenId": "123", // if your contract requires a specific tokenId
// "metadata": "ipfs://...", // if your contract accepts metadata
}
}
]
}
```
```json tokenLocator - EVM theme={null}
{
"lineItems": [
{
"tokenLocator": "ethereum:0x71c7656ec7ab88b098defb751b7401b5f6d897:1234", // ::
"callData": {
"totalPrice": "25.00" // Only totalPrice is required for token purchases
// For external EVM contracts, you might need additional parameters:
// "_amount": 1, // if your contract uses _amount instead of quantity
// "tokenId": "1234", // if your contract requires explicit tokenId
// "metadata": "ipfs://...", // if your contract accepts metadata
}
}
]
}
// To use exact-in mode, add executionParameters: { maxSlippageBps: "500" } to the lineItem object. For custom contract params, add executionParameters: { customParam: "value" }.
```
```json tokenLocator - Solana theme={null}
{
"lineItems": [
{
"tokenLocator": "solana:7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU", // :
"callData": {
"totalPrice": "25.00",
"buyerCreatorRoyaltyPercent": 100 // Required for Solana
}
}
]
}
// To use exact-in mode, add executionParameters: { maxSlippageBps: "500" } to the lineItem object. For custom contract params, add executionParameters: { customParam: "value" }.
```
```json productLocator theme={null}
{
"lineItems": [
{
"productLocator": "amazon:B01DFKC2SO", // Amazon ASIN
// "productLocator": "amazon:https://www.amazon.com/dp/B01DFKC2SO", // Amazon URL
// "productLocator": "shopify:https://your-store.myshopify.com/products/product-name:123456789", // Shopify
}
]
}
```
## Complete API Request Examples
For those who want a full copy/pastable code example, extended versions of the JSON guides are provided below.
```javascript collectionLocator theme={null}
const options = {
method: "POST",
headers: { "x-api-key": "_YOUR_API_KEY_", "Content-Type": "application/json" },
body: JSON.stringify({
recipient: {
email: "buyer@example.com",
},
payment: {
method: "ethereum-sepolia",
currency: "eth",
},
lineItems: [
{
collectionLocator: "crossmint:_YOUR_COLLECTION_ID_", // Crossmint managed collection
// collectionLocator: "crossmint:_YOUR_COLLECTION_ID_:_TEMPLATE_ID_", // With specific template
callData: {
totalPrice: "5.00",
quantity: 1, // matches your contract's parameter name
},
}
],
}),
};
fetch("https://staging.crossmint.com/api/2022-06-09/orders", options)
.then((response) => response.json())
.then((response) => console.log(response))
.catch((err) => console.error(err));
```
```javascript tokenLocator - EVM theme={null}
const options = {
method: "POST",
headers: { "x-api-key": "_YOUR_API_KEY_", "Content-Type": "application/json" },
body: JSON.stringify({
recipient: {
email: "buyer@example.com",
},
payment: {
method: "ethereum-sepolia",
currency: "eth",
},
lineItems: [
{
tokenLocator: "ethereum:0x71c7656ec7ab88b098defb751b7401b5f6d897:1234", // ::
callData: {
totalPrice: "25.00", // Only totalPrice is required for token purchases
// For external EVM contracts, you might need additional parameters:
// _amount: 1, // if your contract uses _amount instead of quantity
// tokenId: "1234", // if your contract requires explicit tokenId
// metadata: "ipfs://...", // if your contract accepts metadata
},
}
],
}),
};
fetch("https://staging.crossmint.com/api/2022-06-09/orders", options)
.then((response) => response.json())
.then((response) => console.log(response))
.catch((err) => console.error(err));
```
```javascript tokenLocator - Solana theme={null}
const options = {
method: "POST",
headers: { "x-api-key": "_YOUR_API_KEY_", "Content-Type": "application/json" },
body: JSON.stringify({
recipient: {
email: "buyer@example.com",
},
payment: {
method: "solana",
currency: "sol",
},
lineItems: [
{
tokenLocator: "solana:7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU", // :
callData: {
totalPrice: "25.00",
buyerCreatorRoyaltyPercent: 100, // Required for Solana
},
}
],
}),
};
fetch("https://staging.crossmint.com/api/2022-06-09/orders", options)
.then((response) => response.json())
.then((response) => console.log(response))
.catch((err) => console.error(err));
```
```javascript productLocator theme={null}
const options = {
method: "POST",
headers: { "x-api-key": "_YOUR_API_KEY_", "Content-Type": "application/json" },
body: JSON.stringify({
recipient: {
email: "buyer@example.com",
physicalAddress: {
name: "John Doe",
line1: "123 Main St",
city: "San Francisco",
state: "CA",
postalCode: "94105",
country: "US",
},
},
payment: {
method: "ethereum-sepolia",
currency: "eth",
},
lineItems: [
{
productLocator: "amazon:B01DFKC2SO", // Amazon ASIN
// productLocator: "amazon:https://www.amazon.com/dp/B01DFKC2SO", // Amazon URL
// productLocator: "shopify:https://your-store.myshopify.com/products/product-name:123456789", // Shopify
}
],
}),
};
fetch("https://staging.crossmint.com/api/2022-06-09/orders", options)
.then((response) => response.json())
.then((response) => console.log(response))
.catch((err) => console.error(err));
```
```javascript collectionLocator - imported contract theme={null}
const options = {
method: "POST",
headers: { "x-api-key": "_YOUR_API_KEY_", "Content-Type": "application/json" },
body: JSON.stringify({
recipient: {
email: "buyer@example.com",
},
payment: {
method: "ethereum-sepolia",
currency: "eth",
},
lineItems: [
{
collectionLocator: "ethereum:0x71c7656ec7ab88b098defb751b7401b5f6d897", // External collection registered in Crossmint Console
callData: {
totalPrice: "5.00",
quantity: 1, // matches your contract's parameter name
// For external contracts, you might need additional parameters such as:
// _amount: 1, // if your mint function uses _amount instead of quantity
// tokenId: "123", // if your contract requires a specific tokenId
// metadata: "ipfs://...", // if your contract accepts metadata
},
}
],
}),
};
fetch("https://staging.crossmint.com/api/2022-06-09/orders", options)
.then((response) => response.json())
.then((response) => console.log(response))
.catch((err) => console.error(err));
```
For more details on physical product purchases, see our guides for [Amazon
Integration](/payments/headless/guides/providers/amazon) and [Shopify
Integration](/payments/headless/guides/providers/shopify).
# Localization
Source: https://docs.crossmint.com/payments/headless/guides/localization
Customize the language of receipts and currency of the checkout
## Currencies and Languages Available
| Variable | Possible Values | Description |
| ------------------ | --------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `locale` | `en-US` `de-DE` `es-ES` `fr-FR` `it-IT` `ja-JP` `ko-KR` `pt-PT` `ru-RU` `th-TH` `tr-TR` `uk-UA` `vi-VN` `zh-CN` `zh-TW` `Klingon` | Language selected for the email receipt. |
| `payment.currency` | `usd` `aud` `eur` `gbp` `hkd` `inr` `jpy` `krw` `sgd` `vnd` | Currency for the payment quote. The original price of the collection will be automatically converted to the selected currency using the most recent bank exchange rates. |
Localization is not supported for stablecoins. While `payment.crypto.defaultCurrency` can be set to `usdc` or other
supported tokens, localized stablecoins like `eurc` are not available.
To customize the language use the property `locale` and for currency, set `currency` under the payment object. Here's an example:
```json {2,6} theme={null}
{
"locale": "es-ES", // Price for receipt (e.g. Spanish customers)
"payment": {
"receiptEmail": "jsmith@example.com",
"method": "card",
"currency": "eur" // If collection is priced in USD, price returned for quote will be in EUR
},
"lineItems": [
{
"collectionLocator": "crossmint:76ced33d-fec8-4741-a69f-450f1ae09fa6",
"callData": {
"totalPrice": ""
}
}
]
}
```
When implementing a headless checkout, you'll need to handle the display of localized content in your own UI based
on the locale you've selected. The locale only affects the email receipt language, not the checkout interface built
by you.
# Multiple Line Items
Source: https://docs.crossmint.com/payments/headless/guides/multiple-line-items
How to handle orders with multiple NFTs at once
The Headless Checkout multiple line items feature is used to build more elaborate purchase experiences, which require multiple NFT purchases at once. Multiple line items is currently only supported for NFTs.
Currently, there is a maximum limit of 15 NFTs per order.
Example use cases include:
* A marketplace where a buyer can add multiple NFTs to a cart and make the purchase in a single transaction.
* Enabling the purchase of multiple distinct tokens from an ERC-1155 contract in a single transaction.
## Marketplace Sales
The following example requires that you have a custom `collectionId` that supports secondary sales provisioned for you by your Crossmint Customer Success Engineer. For more information on marketplace and launchpad support, check [this guide](/payments/advanced/marketplaces-and-launchpads).
If you need to contact the team about getting set up with secondary sales support [contact the team](https://www.crossmint.com/contact/sales).
### Multiple `lineItems` for Secondary Sales
To enable a multi item purchase, you simply need to pass an array of `lineItems` instead of a single object when creating an order.
The below examples demonstrate how to create a multiple line item order for secondary sales:
```bash cURL theme={null}
curl --request POST \
--url https://staging.crossmint.com/api/2022-06-09/orders \
--header 'Content-Type: application/json' \
--header 'x-api-key: _YOUR_API_KEY_' \
--data '{
"recipient": {
"email": "testy@crossmint.com"
},
"locale": "en-US",
"payment": {
"receiptEmail": "testy@crossmint.com",
"method": "ethereum-sepolia",
"currency": "eth"
},
"lineItems": [
{
"collectionLocator": "crossmint:_YOUR_SECONDARY_SALES_COLLECTION_ID_",
"callData": {
"contractAddress": "0x___CONTRACT_ADDRESS_OF_TOKEN",
"tokenId": "234"
}
},
{
"collectionLocator": "crossmint:_YOUR_SECONDARY_SALES_COLLECTION_ID_",
"callData": {
"contractAddress": "0x___ANOTHER_TOKEN_CONTRACT",
"tokenId": "567"
}
}
]
}'
```
```javascript Node.js theme={null}
const options = {
method: 'POST',
headers: {'x-api-key': '_YOUR_API_KEY_', 'Content-Type': 'application/json'},
body: '{
"recipient": {
"email": "test@crossmint.com"
},
"locale": "en-US",
"payment": {
"receiptEmail": "test@crossmint.com",
"method": "ethereum-sepolia",
"currency": "eth",
"payerAddress": "0x1234_payer_address"
},
"lineItems": [
{
"collectionLocator": "crossmint:_YOUR_SECONDARY_SALES_COLLECTION_ID_",
"callData": {
"contractAddress": "0x___CONTRACT_ADDRESS_OF_TOKEN",
"tokenId": "234"
}
},
{
"collectionLocator": "crossmint:_YOUR_SECONDARY_SALES_COLLECTION_ID_",
"callData": {
"contractAddress": "0x___ANOTHER_TOKEN_CONTRACT",
"tokenId": "567"
}
}
]
}'
};
fetch('https://staging.crossmint.com/api/2022-06-09/orders', options)
.then(response => response.json())
.then(response => console.log(response))
.catch(err => console.error(err));
```
After this step you will collect payment from the buyer as outlined in the [Order Lifecycle](/payments/headless/guides/order-lifecycle/) section.
## Primary Sales of Multiple Items
If you have a custom ERC-721 contract that supports the minter being able to specify which token they want to mint
you could use the example(s) below to enable them to purchase multiple and specific tokens in the same transaction.
This is an uncommon pattern for ERC-721 contracts though.
In the following example, the use case is purchasing two unique tokens from an ERC-1155 contract. You can see that the `_id` passed in the `callData` of each `lineItem` is unique (1 and 2).
```bash cURL theme={null}
curl --request POST \
--url https://staging.crossmint.com/api/2022-06-09/orders \
--header 'Content-Type: application/json' \
--header 'x-api-key: _YOUR_API_KEY_' \
--data '{
"recipient": {
"email": "testy@crossmint.com"
},
"locale": "en-US",
"payment": {
"receiptEmail": "testy@crossmint.com",
"method": "ethereum-sepolia",
"currency": "eth"
},
"lineItems": [
{
"collectionLocator": "crossmint:_YOUR_COLLECTION_ID_",
"callData": {
"_id_": "1",
"quantity": "1"
}
},
{
"collectionLocator": "crossmint:_YOUR_COLLECTION_ID_",
"callData": {
"_id_": "2",
"quantity": "1"
}
},
]
}'
```
```javascript Node.js theme={null}
const options = {
method: 'POST',
headers: {'x-api-key': '_YOUR_API_KEY_', 'Content-Type': 'application/json'},
body: '{
"recipient":
{
"email": "testy@crossmint.com"
},
"locale": "en-US",
"payment":
{
"receiptEmail": "testy@crossmint.com",
"method": "ethereum-sepolia",
"currency": "eth",
"payerAddress": "0x1234_payer_address"
},
"lineItems": [
{
"collectionLocator": "crossmint:_YOUR_COLLECTION_ID_",
"callData": {
"_id_": "1",
"quantity": "1"
}
},
{
"collectionLocator": "crossmint:_YOUR_COLLECTION_ID_",
"callData": {
"_id_": "2",
"quantity": "1"
}
},
]
}'
};
fetch('https://staging.crossmint.com/api/2022-06-09/orders', options)
.then(response => response.json())
.then(response => console.log(response))
.catch(err => console.error(err));
```
As in the secondary sales example above, you'll need to handle the remaining steps of the order lifecycle in your UI - payment, delivery, etc.
## Error Handling
As outlined in the [Delivery Phase](/payments/headless/guides/order-lifecycle/delivery-phase#error-handling) of the order lifecycle guides, it is important to keep track of the delivery for each item passed when you created the order and report back to the user on the status of the order.
Each line item is attempted and processed independently. If one line item fails, others will still go through and be fulfilled. If any items are undeliverable, the buyer will be automatically refunded for that specific portion of their order.
For definitions of delivery and line item statuses, see the Status Codes page.
# Order Complete Phase
Source: https://docs.crossmint.com/payments/headless/guides/order-lifecycle/completed-phase
Completion phase of the order lifecycle
The final phase in the order lifecycle is order completion. This occurs once payment has been successful and the delivery phase is finished.
The `order.phase` property will be set to `completed` once all line items have reached a final state — whether each item individually succeeded or failed.
For reference on all statuses (quote, payment, delivery), see the [Status Codes](/payments/headless/guides/status-codes) page.
`order.phase === "completed"` does **not** mean every line item succeeded. The phase reaches `completed` once all
line items have reached a final state — whether `completed` or `failed`. This applies to **both** single-item and multi-item orders.
You must always check each line item's `delivery.status` individually.
### Checking Line Item Delivery Status
Once `order.phase` reaches `completed`, fetch the order via the GET order API and loop through the `lineItems` array to determine the outcome of each item. Each line item's `delivery.status` can be one of: `awaiting-payment`, `in-progress`, `completed`, or `failed`.
```tsx theme={null}
const apiUrl = "https://staging.crossmint.com/api/2022-06-09";
const res = await fetch(`${apiUrl}/orders/${orderId}`, {
method: "GET",
headers: {
"Content-Type": "application/json",
"x-api-key": process.env.NEXT_PUBLIC_CROSSMINT_API_KEY,
authorization: clientSecret,
},
});
const order = await res.json();
if (order.phase === "completed") {
for (const item of order.lineItems) {
if (item.delivery.status === "failed") {
// Handle item failure
} else if (item.delivery.status === "completed") {
// Handle successful delivery
}
}
}
```
### Recommendations
Think through and implement the call to action that makes sense for your buyers.
#### Where can they access the purchased item?
If the token was minted to their Crossmint wallet you can provide a direct link and/or render a representation of it.
Even if they minted directly to an existing wallet you can provide a link to display the token in Crossmint. Use the following URL format to display NFTs within Crossmint:
`https://www.crossmint.com/user/collection/::`
For info on the values to use for `` refer to the [supported chains](/introduction/supported-chains) page.
#### What are the next steps?
Can they stake, trade, or utilize the purchase in some way? Tell them how!
#### Handling Order Failure
If there were any issues with delivery explain what happened and ensure they know that refunds are automatic. Anyone who purchases through Crossmint and has an issue with delivery can contact our support team for assistance.
# Pay with Card
Source: https://docs.crossmint.com/payments/headless/guides/order-lifecycle/credit-card-payment-phase
Payment acceptance phase of the order lifecycle for credit cards.
The order lifecycle can be summarized as follows:
Your application determines recipient info for the buyer. It can be an email wallet or wallet address. Create or
update an order with this info to proceed to next step.
The API response returned from create/update order call(s) will include a `clientSecret` and `order` object that your
application uses to render the Crossmint Embedded Component.
Use the `clientSecret` and `order.orderId` returned in the API response to render the Crossmint Embedded Component
for credit card checkout.
The buyer completes checkout via credit card.
Your application will poll the GET order status and update the UI as the order progresses to the next phase.
During the initial `quote` phase of the order the payment status will be `requires-quote`.
Once the quote phase is completed, the order enters the payment phase and will have the status `awaiting-payment`, which indicates that the order is ready to be paid.
For the complete, authoritative list of payment statuses and their meanings, see the [Status Codes](/payments/headless/guides/status-codes) page. |
### Render the Crossmint Embedded Component
When the order is ready to accept payment, the API response will include a `clientSecret` and `order` object. You can use these with the Crossmint Embedded Component to collect the user's credit card payment.
```json theme={null}
{
"clientSecret": "_YOUR_CLIENT_SECRET_",
"order": {
"orderId": "5ddc0090-7f63-4f6a-b68d-a91f8253b02e",
"phase": "payment",
"locale": "en-US",
"lineItems": [], // removed for brevity
"quote": {
"status": "valid",
"quotedAt": "_timestamp_",
"expiresAt": "_timestamp_",
"totalPrice": {
"amount": "0.5",
"currency": "usd"
}
},
"payment": {
"status": "awaiting-payment",
"method": "basis-theory",
"currency": "usd",
"preparation": {},
"receiptEmail": "test@example.com"
}
}
}
```
After creating an order, you'll need to render the Crossmint Embedded Component to collect payment information. This secure, embeddable UI component lets you accept payment methods, validates input, and handles errors:
```tsx theme={null}
import { CrossmintEmbeddedCheckout, CrossmintProvider } from "@crossmint/client-sdk-react-ui";
export function MyCheckoutPage({ clientSecret, order, recipientWalletAddress, receiptEmail }) {
return (
);
}
```
The `CrossmintEmbeddedCheckout` component is highly customizable. You can adjust colors, styling, layout, and behavior to match your brand. Learn more in our [UI customization guide](/payments/embedded/guides/ui-customization).
### Poll for Status Updates
### Handling Refunded Payments
When polling for order status, you may encounter a situation where `payment.status` is `completed` but the order also contains a `payment.refunded` property. This indicates that the payment was initially successful but has since been refunded.
```json theme={null}
{
"order": {
"payment": {
"status": "completed",
"refunded": {
"amount": "1.00",
"currency": "usd"
}
}
}
}
```
The `payment.refunded` object includes the following fields:
* `amount`: The amount that was refunded
* `currency`: The currency of the refund
When you encounter this state, your application should:
1. Display an appropriate message to the user indicating that their payment was refunded
2. Prevent any further actions related to the order (such as delivery expectations)
3. Provide options for the user to place a new order if desired
This state typically occurs when there was an issue with processing the order after payment was received, such as insufficient liquidity for memecoin purchases or compliance issues.
# Pay with Crypto
Source: https://docs.crossmint.com/payments/headless/guides/order-lifecycle/crypto-payment-phase
Payment acceptance phase of the order lifecycle for pay with crypto
An overview of the process works as follows:
Your user selects the chain, token, and wallet they want to pay from via your application and you create or
update an existing order with this information.
The API response returned from create or update order will include a `serializedTransaction` that your
application uses to request payment from the user's wallet.
After your application initiates the payment request the user must confirm the transaction.
Your application will poll the GET order status and update the UI as the order progresses to the next phase.
During the initial `quote` phase of the order the payment status will be `requires-quote`.
Once the quote phase is completed, the order enters the payment phase.
For most orders the payment phase will begin with the status `awaiting-payment`, which indicates that the order is ready to be paid. It can also begin with `requires-crypto-payer-address` if the payer address is missing.
For the complete, authoritative list of payment statuses and their meanings, see the [Status Codes](/payments/headless/guides/status-codes) page. |
### Setting the Payer Address
The order must know the address that will be sending the crypto payment. This enables Crossmint's payment listeners to associate incoming transactions with the correct order. To update the order with the payer address, call the update API as demonstrated below:
`PATCH` `/api/2022-06-09/orders/`
```json theme={null}
{
"payment": {
"currency": "eth",
"method": "base-sepolia",
"payerAddress": "0x1234abcd…"
}
}
```
The `payerAddress` is the wallet the user will be sending the payment from. Note that you must send the entire payment object even if the currency and/or method values are not changing.
### Submitting the Payment
When you've fully prepared the order such that the payment status is `awaiting-payment` you'll have everything necessary to request the crypto payment from your user. The details will be returned in the `order.payment.preparation` property.
```json theme={null}
{
"orderId": "c167db0f-0cb9-4c59-80d3-aface6bcb338",
"phase": "payment",
"locale": "en-US",
"lineItems": [], // removed for brevity
"quote": // removed for brevity
"payment": {
"status": "awaiting-payment",
"method": "base-sepolia",
"currency": "eth",
"preparation": {
"chain": "base-sepolia",
"payerAddress": "0x6C3b3225759Cbda68F96378A9F0277B4374f9F06",
"serializedTransaction": "0x02f9015083014a34238489173700848917387582653d94a105c311fa72b8fb78c992ecbdb8b02ea5bd394d868644e0f88d81b9011e2d2d2d2d2d2d424547494e204d454d4f2d2d2d2d2d2d65794a68624763694f694a49557a49314e694973496e523563434936496b705856434a392e65794a756232356a5a534936496a4e6b5954673159324a6b4c546c6a4e4459744e4459774f4331694d44686a4c5746694e6d557a595746684e4468694d794973496d39795a4756795357526c626e52705a6d6c6c63694936496d4d784e6a646b596a426d4c54426a596a6b744e474d314f5330344d47517a4c57466d59574e6c4e6d4a6a596a4d7a4f434973496d6c68644349364d5463784f446b784d7a63774f48302e75574365534961563642504b6f6935724d7939394c2d4b56303256644d4d343442343934724c4352656f632d2d2d2d2d2d454e44204d454d4f2d2d2d2d2d2dc0"
}
}
}
```
The `order.payment.preparation` property contains details about the chain that Crossmint is expecting the payment to be received on, the payer address, and a `serializedTransaction` that you can use to open a payment request in the user's wallet. See examples of how to parse the response and request transaction confirmation from the user below:
```jsx theme={null}
import { parseTransaction } from "viem";
import { useSendTransaction } from "wagmi";
const { sendTransactionAsync } = useSendTransaction();
const signAndSendTransaction = async (serializedTransaction) => {
const txn = parseTransaction(serializedTransaction)
try {
const txId = await sendTransactionAsync(txn);
console.log("Transaction ID:", txId);
} catch (error) {
console.error("Error sending transaction:", error);
}
};
```
```jsx theme={null}
import bs58 from "bs58";
import { useConnection, useWallet } from "@solana/wallet-adapter-react";
const { connection } = useConnection();
const { sendTransaction } = useWallet();
const signAndSendTransaction = async (serializedTransaction) => {
const transaction = Transaction.from(bs58.decode(serializedTransaction));
try {
const txId = await sendTransaction(transaction, connection);
console.log("Transaction ID:", txId);
} catch (error) {
console.error("Error sending transaction:", error);
}
}
```
You should **never** alter the values in the parsed transaction object. Simply parse the transaction object as shown
in the example above. Changing any of these values may result in Crossmint not being able to validate the payment.
Calling the `signAndSendTransaction` function in the code snippet(s) above will open the user's wallet and enable them to confirm the crypto payment.
### Awaiting Payment Confirmation
We recommend a polling interval of about 2500ms and never below 500ms.
You can use client-side or server-side API keys to implement headless checkout. Check the code samples for the type
of API key you're using in your application.
```tsx theme={null}
const getOrderPaymentStatus = async () => {
const apiUrl = "https://staging.crossmint.com/api/2022-06-09";
try {
const res = await fetch(`${apiUrl}/orders/${order.orderId}`, {
method: "GET",
headers: {
"Content-Type": "application/json",
"x-api-key": process.env.NEXT_PUBLIC_CROSSMINT_API_KEY,
authorization: clientSecret, // saved from response of create order call
},
});
const refreshedOrder = await res.json();
setOrder(refreshedOrder);
return refreshedOrder.payment.status;
} catch (e) {
console.error(e);
throw new Error("Failed to fetch order");
}
};
const pollPaymentStatus = async () => {
const intervalId = setInterval(async () => {
const status = await getOrderPaymentStatus();
console.log("payment status: ", status);
if (status === "completed") {
clearInterval(intervalId);
}
}, 2500);
// Set a timeout to stop polling after 60 seconds
setTimeout(() => {
clearInterval(intervalId);
console.log("Taking longer than expected...");
}, 60000);
};
```
The sample code below is from a NextJS application. The `component.tsx` file is simplified to only show the relevant
logic. The client-side component sends an API request to the application's backend, which then proxies the request
to Crossmint. This is because the example is using a server-side API key, which requires making requests from a server environment.
```tsx component.tsx (client-side) theme={null}
const getOrderPaymentStatus = async () => {
try {
const res = await fetch(`/orders/${order.orderId}`, {
method: "GET",
headers: {
"Content-Type": "application/json",
},
});
const refreshedOrder = await res.json();
setOrder(refreshedOrder);
return refreshedOrder.payment.status;
} catch (e) {
console.error(e);
throw new Error("Failed to fetch order");
}
};
const pollPaymentStatus = async () => {
const intervalId = setInterval(async () => {
try {
const status = await getOrderPaymentStatus();
console.log("payment status: ", status);
if (status === "completed") {
clearInterval(intervalId);
}
} catch (e) {
clearInterval(intervalId);
console.error("Error polling payment status: ", e);
}
}, 2500);
// Set a timeout to stop polling after 60 seconds
setTimeout(() => {
clearInterval(intervalId);
console.log("Taking longer than expected...");
}, 60000);
};
```
```typescript route.ts (server-side) theme={null}
import { callCrossmintAPI } from "@/app/utils/crossmint";
import { NextRequest, NextResponse } from "next/server";
export async function GET(req: NextRequest, { params }: { params: { orderId: string } }) {
if (params.orderId) {
const order = await callCrossmintAPI(`/orders/${params.orderId}`, {
method: "GET",
});
return NextResponse.json(order, { status: 200 });
} else {
return NextResponse.json({ error: true, message: "Missing orderId" }, { status: 400 });
}
}
```
```typescript crossmint.ts theme={null}
const crossmintBaseUrl = process.env.CROSSMINT_API_URL;
const crossmintAPIHeaders = {
accept: "application/json",
"content-type": "application/json",
"x-api-key": process.env.CROSSMINT_API_KEY!,
};
const callCrossmintAPI = async (endpoint: string, options: { method: string; body?: any; params?: any }) => {
const url = `${crossmintBaseUrl}/${endpoint}`;
const { body, method } = options;
const response = await fetch(url, {
body: body ? JSON.stringify(body) : null,
method,
headers: crossmintAPIHeaders,
});
const json = await response.json();
return json;
};
export { callCrossmintAPI };
```
Once the payment is confirmed, you can move on to the delivery phase of the order lifecycle.
### Handling Refunded Payments
When polling for order status, you may encounter a situation where `payment.status` is `completed` but the order also contains a `payment.refunded` property. This indicates that the payment was initially successful but has since been refunded.
```json theme={null}
{
"order": {
"payment": {
"status": "completed",
"refunded": {
"amount": "1.23",
"currency": "eth"
}
}
}
}
```
The `payment.refunded` object includes the following fields:
* `amount`: The amount that was refunded
* `currency`: The currency of the refund
When you encounter this state, your application should:
1. Display an appropriate message to the user indicating that their payment was refunded
2. Prevent any further actions related to the order (such as delivery expectations)
3. Provide options for the user to place a new order if desired
This state typically occurs when there was an issue with processing the order after payment was received, such as insufficient liquidity for memecoin purchases or compliance issues.
# Delivery Phase
Source: https://docs.crossmint.com/payments/headless/guides/order-lifecycle/delivery-phase
Token delivery phase of the order lifecycle
When the payment phase is completed, the user is guaranteed to receive the items ordered or an automatic refund for any unfulfilled items. Upon payment completion you'll want to display an animation in the UI indicating that the delivery is in-progress. Then poll the GET order API to check on delivery status and update your application UI when delivery status is updated.
You can use client-side or server-side API keys to implement headless checkout. Check the code samples for the type
of API key you're using in your application.
We recommend a polling interval of about 2500ms and never below 500ms.
```tsx theme={null}
const getOrderDeliveryStatus = async () => {
const apiUrl = "https://staging.crossmint.com/api/2022-06-09";
try {
const res = await fetch(`${apiUrl}/orders/${order.orderId}`, {
method: "GET",
headers: {
"Content-Type": "application/json",
"x-api-key": process.env.NEXT_PUBLIC_CROSSMINT_API_KEY,
authorization: clientSecret, // saved from response of create order call
},
});
const refreshedOrder = await res.json();
setOrder(refreshedOrder);
return refreshedOrder.lineItems[0].delivery.status;
} catch (e) {
console.error(e);
throw new Error("Failed to fetch order");
}
};
const pollDeliveryStatus = async () => {
const intervalId = setInterval(async () => {
try {
const status = await getOrderDeliveryStatus();
console.log("delivery status: ", status);
if (status === "completed") {
clearInterval(intervalId);
}
} catch (e) {
clearInterval(intervalId);
console.error("Error polling delivery status: ", e);
}
}, 2500);
// Set a timeout to stop polling after 60 seconds
setTimeout(() => {
clearInterval(intervalId);
console.log("Taking longer than expected...");
}, 60000);
};
```
The sample code below is from a NextJS application. The `component.tsx` file is simplified to only show the relevant
logic. The client-side component sends an API request to the application's backend, which then proxies the request
to Crossmint. This is because the example is using a server-side API key, which requires making requests from a
server environment.
```tsx component.tsx (client-side) theme={null}
const getOrderDeliveryStatus = async () => {
try {
const res = await fetch(`/orders/${order.orderId}`, {
method: "GET",
headers: {
"Content-Type": "application/json",
},
});
const refreshedOrder = await res.json();
setOrder(refreshedOrder);
return refreshedOrder.lineItems[0].delivery.status;
} catch (e) {
console.error(e);
throw new Error("Failed to fetch order");
}
};
const pollDeliveryStatus = async () => {
const intervalId = setInterval(async () => {
const status = await getOrderDeliveryStatus();
console.log("delivery status: ", status);
if (status === "completed") {
clearInterval(intervalId);
}
}, 2500);
// Set a timeout to stop polling after 60 seconds
setTimeout(() => {
clearInterval(intervalId);
console.log("Taking longer than expected...");
}, 60000);
};
```
```typescript route.ts (server-side) theme={null}
import { callCrossmintAPI } from "@/app/utils/crossmint";
import { NextRequest, NextResponse } from "next/server";
export async function GET(req: NextRequest, { params }: { params: { orderId: string } }) {
if (params.orderId) {
const order = await callCrossmintAPI(`/orders/${params.orderId}`, {
method: "GET",
});
return NextResponse.json(order, { status: 200 });
} else {
return NextResponse.json({ error: true, message: "Missing orderId" }, { status: 400 });
}
}
```
```typescript crossmint.ts theme={null}
const crossmintBaseUrl = process.env.CROSSMINT_API_URL;
const crossmintAPIHeaders = {
accept: "application/json",
"content-type": "application/json",
"x-api-key": process.env.CROSSMINT_API_KEY!,
};
const callCrossmintAPI = async (endpoint: string, options: { method: string; body?: any; params?: any }) => {
const url = `${crossmintBaseUrl}/${endpoint}`;
const { body, method } = options;
const response = await fetch(url, {
body: body ? JSON.stringify(body) : null,
method,
headers: crossmintAPIHeaders,
});
const json = await response.json();
return json;
};
export { callCrossmintAPI };
```
### Error Handling
Once the payment has been confirmed, delivery is likely to complete successfully. Before accepting the payment Crossmint simulates the expected transaction, which means that most delivery issues will be prevented before payment is even accepted. Given a successful payment the delivery process will be retried until it completes successfully or determines that delivery is impossible.
The most common cause for a delivery failure is that the item is no longer available. In the case of primary sales this could mean that the collection is sold out. For secondary sales the reason could be that someone else bought the item first.
Your application should be aware of the potential for delivery to fail and provide information to the buyer on what to expect. In the case of multiple items purchased and partial delivery failure you'll need to indicate which items were successfully delivered and which items failed.
Be sure to let your buyer know that in the case of a failed delivery they will be refunded automatically. If they
have any questions or issues they can reach out to Crossmint support directly for assistance.
A receipt will be sent to the buyer as long as their email was included in the order. There are two ways to pass this information. If the recipient is set to an email address, it will be used to send the receipt. In the case that the recipient is set to a `walletAddress` you can include an optional `receiptEmail` property within the payment object.
```json Crypto Payment theme={null}
{
"payment": {
"method": "base-sepolia",
"currency": "eth",
"payerAddress": "0xabcd1234...",
"receiptEmail": "user@example.com"
},
"recipient": {
"walletAddress": "0xabcd1234..."
}
}
```
```json Credit Card Payment theme={null}
{
"payment": {
"method": "card",
"receiptEmail": "user@example.com"
},
"recipient": {
"email": "user@example.com"
}
}
```
# Quote Phase
Source: https://docs.crossmint.com/payments/headless/guides/order-lifecycle/quote-phase
Initial phase of the order lifecycle
The first phase of the headless checkout process is the quote phase. During this phase, you construct an order that consists of the items being purchased, payment method, locale, and recipient. This will ultimately yield a quote to purchase it.
It's possible for an order to go from the payment phase back to the quote phase if the quote expires.
You can initialize an order such that the quote phase is completed in a single API call. Alternatively, you can create a basic order and iteratively update it with locale, recipient, and payment method information. The only information you cannot edit on a quote is the `lineItems` array, which indicate the details of what is being purchased.
If you need to alter the `lineItems` in an order you must create a new order.
## Quote status overview
During the quote phase, statuses indicate whether the order has enough information to proceed (like a recipient), and whether the quote is still valid. For the full, authoritative list of quote statuses, see the Status Codes page.
See the full list: [Status Codes](/payments/headless/guides/status-codes).
## Creating the Order
You will create the order via API call. The headless checkout offers support for both client-side or server-side API keys to enable you to build in a way that makes sense for your application.
Orders can be created using a client-side API key, but any subsequent fetches or updates will require passing the
`clientSecret` returned in the create-order API response as an `authorization` header. This guide is written with
the expectation of using a server-side API key.
For more information on using Crossmint APIs refer to [API keys](/introduction/platform/api-keys) documentation
page.
To create a minimal order, make a `POST` request to the `/api/2022-06-09/orders` endpoint (view [API reference](/api-reference/headless/create-order)). The required properties to create an order are: `payment` and `lineItems`.
```json Crypto Payment theme={null}
{
"payment": {
"method": "base-sepolia",
"currency": "eth",
"payerAddress": "0x1234abcd..." // optional to create order, but required to proceed to payment phase
},
"lineItems": [
{
"collectionLocator": "crossmint:_YOUR_COLLECTION_ID_",
"callData": {
"totalPrice": "0.0001"
}
}
]
}
```
```json Credit Card Payment theme={null}
{
"payment": {
"method": "card",
"receiptEmail": "your-email@example.com"
},
"lineItems": [
{
"collectionLocator": "crossmint:_YOUR_COLLECTION_ID_",
"callData": {
"totalPrice": "0.0001"
}
}
]
}
```
The `payment` object indicates the method and currency you intend to make the payment with. For crypto payments, this can be the same chain of the NFT or another chain that the buyer has liquidity on.
For example, buying an NFT on `BASE` chain with `BASE` `ETH`, buying an NFT on Solana with `SOL`, or any cross-chain combination of a [supported chain](/introduction/supported-chains) and currency.
The `payment.method` value for credit card checkouts should be `card` and the `payment.currency` any of the [supported fiat currencies](/payments/headless/guides/localization#currencies-and-languages-available).
The `lineItems` array describes the NFTs being purchased.
#### Collection Locator
The `collectionLocator` within `lineItems` is how you specify the collection within Crossmint.
For primary sales where the NFTs are being minted for the first time, the contract will need to be registered in the Crossmint console.
You can find your `collectionId` within the Token collections tab in the developer console.
#### Token Locator
Alternatively, if this is a secondary sale, and your token is already minted and available on a supported marketplace, you can use a `tokenLocator`, instead of a `collectionLocator`.
***Using a token locator means you do not need to register the collection in the Crossmint console.***
The `tokenLocator` for EVM Chains follows the format `blockchain:contractAddress:tokenId` and for Solana follows the format `blockchain:tokenAddress`.
More information on the `tokenLocator` format can be found in the [marketplaces and launchpads guide](/payments/advanced/marketplaces-and-launchpads).
Remember, you can [view orders within the Developer
Console](/payments/advanced/testing-tips#reviewing-orders-in-the-developer-console) to help get insight into the
order process during testing and even after launch.
## Updating the Order
All orders require a recipient to progress to the payment phase. You can also optionally add a specific locale, which is used to indicate the language for the email receipt sent to the buyer.
The primary reason you'll update an existing order is when the buyer changes the payment method or currency they'd like to pay with in the UI that you build. To update the order, make a `PATCH` request to the `/api/2022-06-09/orders/` endpoint to make these changes and receive a response with updated payment details. View [API reference here](/api-reference/headless/edit-order).
You can update all of these fields at the same time or individually depending on what makes the most sense for your use case.
### Update the Recipient
First, take a look at how to add the recipient to an existing order. The `requires-recipient` state occurs when you initialize a minimal order and do not include a recipient value. Valid options for the recipient property are `email` or `walletAddress`.
When you set an email recipient, the token will be minted to a Crossmint custodial wallet that can be accessed via [www.crossmint.com](https://www.crossmint.com/) (or [staging.crossmint.com](https://staging.crossmint.com) during testing).
You can update the recipient for an existing order as follows:
`PATCH` `/api/2022-06-09/orders/`
```json theme={null}
{
"recipient": {
"email": "buyer@example.com"
}
}
```
OR
```json theme={null}
{
"recipient": {
"walletAddress": "0x1234abcd…"
}
}
```
The wallet address **must** be compatible with the chain the NFT is on. For example, a Solana NFT requires a Solana
wallet address and an NFT on an EVM chain requires a compatible EVM wallet address.
### Update Chain and/or Currency
To change details about how the buyer will pay for the NFTs you use the same API route as above and pass a new payment object.
```json Crypto Payment theme={null}
{
"payment": {
"method": "ethereum-sepolia",
"currency": "usdc",
"payerAddress": "0x1234abcd..."
}
}
```
```json Credit Card Payment theme={null}
{
"payment": {
"method": "card",
"receiptEmail": "your-email@example.com"
}
}
```
This will return a new response that can be used to prompt the buyer to complete the crypto payment.
Anytime you update the order you must ensure you use the newly returned `payment.preparation` property to proceed
with the payment process.
### Update the Locale
As mentioned above, the `locale` is used to set the language and currency for the email receipt sent to your buyer. The default is `en-US`.
Below is an example of the body you'd pass to update the locale setting:
```json theme={null}
{
"locale": "es-ES"
}
```
The available locale values are:
`en-US`, `de-DE`, `es-ES`, `fr-FR`, `it-IT`, `ja-JP`, `ko-KR`, `pt-PT`, `ru-RU` `th-TH` `tr-TR` `uk-UA` `vi-VN` `zh-CN` `zh-TW`, `Klingon`
## Quote Expiration
The time before expiration depends on the payment method chosen. Check the `quote.expiresAt` property to determine how long the quote is valid for. Additionally, the `quote.status` property will be set to `expired` if this timeframe is exceeded.
If your quote has expired you'll need to create a new order.
# Payment Methods
Source: https://docs.crossmint.com/payments/headless/guides/payment-methods
Understand the supported payment methods and how to implement them
## Cross-Chain + Crypto Payments
You can easily create or update an order to receive a price quote, enabling your user to pay with crypto they have on the same chain or even liquidity they have on other chains.
There are several considerations you need to keep in mind when building your app to support cross-chain payments. Some recommendations include the following:
* How to test the process out in staging/testnet and then move to production/mainnet
* Filtering down the list of tokens for which the connected wallet has enough balance to cover the purchase
* Ensuring the wallet is set to the same network as the selected payment chain
Refer to the [Order Lifecycle](/payments/headless/guides/order-lifecycle/quote-phase) docs for details about creating and updating orders based on user selections.
For a full list of supported crypto currencies, refer to the [supported
currencies](/payments/headless/guides/supported-currencies) page.
You'll need to build out a UI that enables your buyer to select from the supported currencies, and then update the existing order upon any changes.
Code example(s) coming soon!
## Credit Card Payments
The option of accepting credit card payments via headless checkout is also available. For detailed examples of creating and updating orders, refer to the [Order Lifecycle](/payments/headless/guides/order-lifecycle/quote-phase) docs.
### Test Price Limits and Test Credit Cards
When building your applications using the staging environment, you can use various test credit cards numbers to see the entire process end-to-end, without actually having to transact using a real credit card.
Check out the Testing Tips page for more info on [price limits](/payments/advanced/testing-tips#limits-in-staging) and [test card numbers](/payments/advanced/testing-tips#test-credit-card-numbers).
# Physical Product Purchases
Source: https://docs.crossmint.com/payments/headless/guides/physical-good-purchases
How to purchase physical products with crypto using Headless Checkout
Crossmint's Headless Checkout now supports purchasing physical products using a wallet's crypto balance through the `productLocator` parameter. This guide explains how to integrate physical product purchases and handle shipping requirements.
## Physical Address Requirement
When purchasing physical products, a shipping address is required (currently only US addresses are supported). You can provide this in two ways:
### 1. Include Address During Order Creation
```json theme={null}
POST /api/2022-06-09/orders
{
"recipient": {
"email": "buyer@example.com",
"physicalAddress": {
"name": "John Doe", // required
"line1": "123 Main St", // required
"line2": "Apt 4B", // optional
"city": "San Francisco", // required
"state": "CA", // required for US addresses
"postalCode": "94105", // required
"country": "US" // required - only US is currently supported
}
}
}
```
### 2. Update Address After Order Creation
If you create an order without specifying a `physicalAddress`, the quote cannot proceed until a valid shipping address is provided. See the full list of statuses on the Status Codes page.
To resolve this, update the order with the physical address:
```json theme={null}
PATCH /api/2022-06-09/orders/{orderId}
{
"recipient": {
"physicalAddress": {
"name": "John Doe", // required
"line1": "123 Main St", // required
"line2": "Apt 4B", // optional
"city": "San Francisco", // required
"state": "CA", // required for US addresses
"postalCode": "94105", // required
"country": "US" // required - only US is currently supported
}
}
}
```
After providing the physical address, the order status will update and include the necessary `payment.preparation` parameters to proceed with payment.
## Order Status Handling
When purchasing physical products, provide a shipping address up front or update the order after creation. Until a valid address is present, the order cannot proceed to payment preparation. For the complete, authoritative list of statuses and their meanings, see the Status Codes page.
## Supported Providers
### Amazon
Amazon products can be purchased using either their ASIN or product URL. See the [Amazon Integration Guide](/payments/headless/guides/providers/amazon) for details.
### Shopify
Shopify products can be purchased using the product URL and variant ID. See the [Shopify Integration Guide](/payments/headless/guides/providers/shopify) for details.
# Order Errors
Source: https://docs.crossmint.com/payments/headless/guides/physical-product-order-errors
Common reasons why physical product orders may fail and how to resolve them
When purchasing physical products through Crossmint, your order may fail for various reasons. This guide explains the most common restrictions and requirements for different providers.
## Amazon
Amazon orders through Crossmint have specific requirements that must be met for successful processing. If your Amazon order fails, it may be due to one of the following restrictions:
### 1. Item must be sold by Amazon or a verified third-party seller
Only products sold directly by Amazon or through verified third-party sellers on the Amazon marketplace are supported. Items from unverified sellers or those with questionable seller ratings may be rejected.
### 2. Item must have standard shipping options available
The product must support standard shipping methods. Items that require special shipping arrangements, expedited delivery only, or have shipping restrictions may not be available for purchase.
### 3. Item must be a physical product (not digital goods or services)
Only physical products that can be shipped are supported. The following types of items are **not supported**:
* Digital downloads (software, music, ebooks)
* Digital gift cards
* Services or subscriptions
* Virtual products
### 4. Item must not be from specialty Amazon programs
Products from the following Amazon specialty programs are **not supported**:
* **Amazon Fresh** - Fresh grocery delivery service
* **Amazon Pantry** - Bulk household items program
* **Amazon Pharmacy** - Prescription medications and health products
* **Subscribe & Save** - Subscription-only items that require recurring delivery
## Need Help?
If you continue to experience issues after checking these requirements, please [contact support](https://portal.usepylon.com/crossmint/forms/contact-support) with:
* The specific product URL or identifier you're trying to purchase
* The complete error message you received
* Your shipping address details (city, state, country)
This information will help us quickly identify and resolve the issue with your order.
# Plan Your Solution
Source: https://docs.crossmint.com/payments/headless/guides/plan-your-solution
Detailed integration guide to build out a fully custom digital asset checkout experience
The Headless Checkout suite of APIs offers the most sophisticated tooling available to build fully custom experiences for your users. This guide will walk you through the entire integration process in a logical order. The first step is to take a few minutes to plan out your solution.
## Client or Server Approach
Depending on how your application is architected, you may want to use server-side or client-side API keys. Check out the [client or server guide](/payments/headless/guides/client-or-server) for more info.
## Decide on Payment Methods
The Headless Checkout supports cross-chain crypto payments and credit cards. Decide if you want to enable both credit card and crypto payments, and which cross-chain tokens should be available.
### Cross-Chain Payments
Enable your users to pay for digital assets directly with crypto or with liquidity they have available on other chains by leveraging the cross-chain payments support.
Check the [cross-chain payments](/payments/headless/guides/payment-methods#cross-chain-payments) section for more in-depth guidance.
### Credit Card Payments
You can also use headless checkout to initiate credit card orders for your buyers.
Check the [credit card payments](/payments/headless/guides/payment-methods#credit-card-payments) section for more in-depth guidance.
## Staging or Production
In most cases, it makes sense to start the integration process in the staging environment where all features are available and free to try. You can find more information about [Staging and Production environments here](/introduction/platform/staging-vs-production).
Headless checkout requires enablement by the Customer Success Engineering team in production. [Contact
Sales](https://www.crossmint.com/contact/sales) if your don't already have a CSE assigned.
## Create or Import Your Collection in the Console
For NFTs, you will need to have a collection that your users can purchase NFTs from. You can deploy a collection directly from the Crossmint Developer Console or import a contract you've deployed elsewhere. Check these guides for more details on creating or importing collections.
* [Create a Collection](/payments/guides/create-collection)
* [Import a Collection](/payments/guides/register-collection)
Once you have a collection created or imported, you'll have a `collectionId`, which is necessary to create orders.
## Create Your API Key
With the above steps completed, you're ready to create an API key to use for your project.
1. Login to the developer console where your collection was created in the previous step
2. If you're on the collection detail view, navigate back to the home page of the console
3. Click the "Integrate" tab and select the "API Keys" section on top
Before creating an API key, you'll need to decide on server-side vs client-side implementation. You can find some more info in the [Client or Server Guide](/payments/headless/guides/client-or-server).
## Next Steps
You're ready to move on to implementing the Headless Checkout in your application!
# Production Launch
Source: https://docs.crossmint.com/payments/headless/guides/production-launch
Follow this checklist to ensure you are ready for production launch with Headless Checkout
## From Staging to Production with Headless Checkout
***
### Launch Checklist
1. Change all environment URLs from `staging.crossmint.com/api/2022-06-09` to `www.crossmint.com/api/2022-06-09`.
2. Change your API keys to the production versions. *Ensure your production API key has the appropriate scopes enabled, such as `orders.create`, and that all authorized domains and mobile bundle identifiers are properly configured.*
3. Update your collection ID to the production one.
4. As needed, change your passed-in props to be production ready (e.g. email, `payment`, `lineItems`).
```javascript {2,6,20} Production theme={null}
// Using the REST API
fetch("https://www.crossmint.com/api/2022-06-09/orders", {
method: "POST",
headers: {
"Content-Type": "application/json",
"x-api-key": "YOUR_PROD_API_KEY"
},
body: JSON.stringify({
"recipient": {
"email": "steve@gmail.com"
},
"locale": "en-US",
"payment": {
"receiptEmail": "steve@gmail.com",
"method": "ethereum",
"currency": "eth",
"payerAddress": "0x742d35Cc6634C0532925a3b844Bc454e4438f44e"
},
"lineItems": [
{
"collectionLocator": "crossmint:YOUR_PROD_COLLECTION_ID",
"callData": {
"totalPrice": "0.25",
"quantity": 1
}
}
]
})
});
```
```javascript {2,6,20} Staging theme={null}
// Using the REST API
fetch("https://staging.crossmint.com/api/2022-06-09/orders", {
method: "POST",
headers: {
"Content-Type": "application/json",
"x-api-key": "YOUR_STAGING_API_KEY"
},
body: JSON.stringify({
"recipient": {
"email": "test@crossmint.com"
},
"locale": "en-US",
"payment": {
"receiptEmail": "test@crossmint.com",
"method": "ethereum-sepolia",
"currency": "eth",
"payerAddress": "0x1234_payer_address"
},
"lineItems": [
{
"collectionLocator": "crossmint:YOUR_STAGING_COLLECTION_ID",
"callData": {
"totalPrice": "0.001",
"quantity": 1
}
}
]
})
});
```
# Amazon Integration
Source: https://docs.crossmint.com/payments/headless/guides/providers/amazon
How to enable crypto purchases of Amazon products using Headless Checkout
This guide explains how to integrate Amazon product purchases using the Headless Checkout API.
## Product Locator Format
When creating an order for an Amazon product, use the `productLocator` parameter with either the Amazon ASIN or product URL:
```json theme={null}
// Amazon ASIN
{
"lineItems": [
{
"productLocator": "amazon:B01DFKC2SO"
}
]
}
// Amazon Product URL
{
"lineItems": [
{
"productLocator": "amazon:https://www.amazon.com/dp/B01DFKC2SO"
}
]
}
```
## Example Flow
* Specify the receipient's email
* Specify their shipping information
* Specify the payment method used
* Specify the product locator
For a full list of supported currencies, refer to the [supported
currencies](/payments/headless/guides/supported-currencies) page.
```json theme={null}
POST /api/2022-06-09/orders
{
"recipient": {
"email": "buyer@example.com",
"physicalAddress": {
"name": "John Doe", // required
"line1": "123 Main St", // required
"city": "San Francisco", // required
"state": "CA", // required for US addresses
"postalCode": "94105", // required
"country": "US" // required - only US is currently supported
}
},
"payment": {
"method": "ethereum-sepolia",
"currency": "eth"
},
"lineItems": [
{
"productLocator": "amazon:B01DFKC2SO"
}
]
}
```
With all requied information provided, the order's response will include payment preparation details, as shown below.
```json theme={null}
{
"order": {
"orderId": "...",
"quote": {
"status": "valid",
...
},
"payment": {
"preparation": {
... // Payment details will be present here
}
}
}
}
```
## Additional Information
* Monitor order statuses through the [Developer Console](https://www.crossmint.com/console)
* Review the [Order Lifecycle guide](/payments/headless/guides/order-lifecycle/quote-phase) for detailed status handling
* Check [this guide](/payments/headless/guides/payment-methods) for details on supported currencies and payment methods
# Shopify Integration
Source: https://docs.crossmint.com/payments/headless/guides/providers/shopify
How to enable crypto purchases of Shopify products using Headless Checkout
This guide explains how to integrate Shopify product purchases using the Headless Checkout API.
## Product Locator Format
When creating an order for a Shopify product, use the `productLocator` parameter with the Shopify product URL and variant ID:
```json theme={null}
{
"lineItems": [
{
"productLocator": "shopify:https://www.gymshark.com/products/gymshark-arrival-5-shorts-black-ss22:39786362601674"
}
]
}
```
The format for the product locator is `shopify::`, where:
* `shopify:` is the prefix identifying this as a Shopify product
* `` is the full URL to the product page
* `` is the unique variant ID for the specific product option (size, color, etc.)
## Example Flow
* Specify the recipient's email
* Specify their shipping information
* Specify the payment method used
* Specify the product locator
For a full list of supported currencies, refer to the [supported
currencies](/payments/headless/guides/supported-currencies) page.
```json theme={null}
POST /api/2022-06-09/orders
{
"recipient": {
"email": "buyer@example.com",
"physicalAddress": {
"name": "John Doe", // required
"line1": "123 Main St", // required
"city": "San Francisco", // required
"state": "CA", // required for US addresses
"postalCode": "94105", // required
"country": "US" // required - only US is currently supported
}
},
"payment": {
"method": "ethereum-sepolia",
"currency": "eth"
},
"lineItems": [
{
"productLocator": "shopify:https://www.gymshark.com/products/gymshark-arrival-5-shorts-black-ss22:39786362601674"
}
]
}
```
With all required information provided, the order's response will include payment preparation details, as shown below.
```json theme={null}
{
"order": {
"orderId": "...",
"quote": {
"status": "valid",
...
},
"payment": {
"preparation": {
... // Payment details will be present here
}
}
}
}
```
## Additional Information
* Monitor order statuses through the [Developer Console](https://www.crossmint.com/console)
* Review the [Order Lifecycle guide](/payments/headless/guides/order-lifecycle/quote-phase) for detailed status handling
* Check [this guide](/payments/headless/guides/payment-methods) for details on supported currencies and payment methods
* **Note:** Delivery destinations are limited to regions supported by the Shopify store itself. The ability to ship to a specific address depends on the merchant's shipping configuration.
# Specify Recipient
Source: https://docs.crossmint.com/payments/headless/guides/specify-recipient
Select where purchased items should be delivered in Headless Checkout
## Specifying the Recipient
For Headless Checkout, you specify the recipient when creating or updating an order.
### Recipient by Email
When specifying a recipient by email, Crossmint will automatically create a secure custodial wallet on the fly for that email address:
```json {4} theme={null}
POST /api/2022-06-09/orders
{
"recipient": {
"email": "user@example.com" // Email address of the recipient
},
"locale": "en-US",
"payment": {
"method": "ethereum-sepolia",
"currency": "eth",
"payerAddress": "0x1234abcd...",
"receiptEmail": "user@example.com"
},
"lineItems": [
{
"collectionLocator": "crossmint:cosdahdasda",
"callData": {
"totalPrice": "0.0001",
"quantity": 1
}
}
]
}
```
```json {4} theme={null}
PATCH /api/2022-06-09/orders/{orderId}
{
"recipient": {
"email": "user@example.com" // Email address of the recipient
}
}
```
The recipient will be able to access their purchased items by logging into their Crossmint wallet in [staging](https://staging.crossmint.com/user/collection) or [mainnet](https://www.crossmint.com/user/collection).
### Recipient by Wallet Address
To deliver items directly to a specific blockchain wallet address:
```json {4} EVM theme={null}
POST /api/2022-06-09/orders
{
"recipient": {
"walletAddress": "0x1234567890abcdef1234567890abcdef12345678" // For EVM chains
},
"locale": "en-US",
"payment": {
"method": "ethereum-sepolia",
"currency": "eth",
"payerAddress": "0x1234abcd...",
"receiptEmail": "user@example.com" // Optional — recommended when using wallet address
},
"lineItems": [
{
"collectionLocator": "crossmint:_YOUR_EVM_COLLECTION_",
"callData": {
"totalPrice": "0.0001",
"quantity": 1
}
}
]
}
```
```json {4} Solana theme={null}
POST /api/2022-06-09/orders
{
"recipient": {
"walletAddress": "5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty" // For Solana
},
"locale": "en-US",
"payment": {
"method": "solana",
"currency": "sol",
"payerAddress": "5FHne...",
"receiptEmail": "user@example.com" // Optional — recommended when using wallet address
},
"lineItems": [
{
"collectionLocator": "crossmint:_YOUR_SOLANA_COLLECTION_",
"callData": {
"totalPrice": "0.0001",
"quantity": 1
}
}
]
}
```
```json {4} EVM theme={null}
PATCH /api/2022-06-09/orders/{orderId}
{
"recipient": {
"walletAddress": "0x1234567890abcdef1234567890abcdef12345678" // For EVM chains
}
}
```
```json {4} Solana theme={null}
PATCH /api/2022-06-09/orders/{orderId}
{
"recipient": {
"walletAddress": "5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty" // For Solana
}
}
```
If specifying a recipient by wallet address, ensure the address is valid for the chain your collection is on, which
may differ from the chain the payment is performed on.
### Physical Product Recipients
When purchasing physical products, providing a physical address is required. If you don't provide it in the initial order, you can update the order it later:
```json {5-13, 20} theme={null}
POST /api/2022-06-09/orders
{
"recipient": {
"email": "buyer@example.com", // Email address of the recipient
"physicalAddress": {
"name": "John Doe",
"line1": "123 Main St",
"line2": "Apt 4B", // optional
"city": "San Francisco",
"state": "CA",
"postalCode": "94105",
"country": "US"
}
},
"locale": "en-US",
"payment": {
"method": "ethereum-sepolia",
"currency": "eth",
"payerAddress": "0x1234abcd...",
"receiptEmail": "buyer@example.com" // Optional — recommended for physical products
},
"lineItems": [
{
"collectionLocator": "crossmint:cosdahdasda",
"callData": {
"totalPrice": "0.0001",
"quantity": 1
}
}
]
}
```
```json {4-12} theme={null}
PATCH /api/2022-06-09/orders/{orderId}
{
"recipient": {
"physicalAddress": {
"name": "John Doe",
"line1": "123 Main St",
"line2": "Apt 4B", // optional
"city": "San Francisco",
"state": "CA",
"postalCode": "94105",
"country": "US"
}
}
}
```
If you create an order with a physical product without specifying a `physicalAddress`, the quote cannot proceed
until a physical address is provided. After providing the physical address, the order status will update and include
the necessary `payment.preparation` parameters to proceed with payment. See the full list of statuses on the
Status Codes page and learn more about payment preparation in the
payment phase documentation.
# Status Codes
Source: https://docs.crossmint.com/payments/headless/guides/status-codes
Guide to understanding the status codes returned from the Headless Checkout APIs
Crossmint's headless checkout includes status codes for multiple aspects of the checkout process. Using status codes lets you build detailed user experiences and UIs that inform about the status of the payment process and delivery of their purchase.
There is the top level `order.phase` and then there are sub-statuses for `quote`, `payment`, and `delivery`.
## Quote statuses
* `item-unavailable` — No items in the order are available for purchase at the current time
* `valid` — The quote is valid
* `expired` — The quote has expired and needs to be refreshed
* `requires-recipient` — The order is missing recipient information (email or wallet address)
## Payment statuses
* `requires-quote` — The order is still in the quote phase
* `requires-crypto-payer-address` — A crypto wallet address needs to be provided for the payment
* `requires-email` — An email is required to proceed (typically for card payments or receipts)
* `requires-kyc` — The buyer must complete KYC verification to proceed
* `manual-kyc` — KYC requires manual review; the buyer will be notified of the outcome
* `failed-kyc` — KYC verification failed and the buyer cannot proceed
* `crypto-payer-insufficient-funds` — The specified payer address cannot cover the purchase
* `crypto-payer-insufficient-funds-for-gas` — The payer address cannot cover the required network fees
* `awaiting-payment` — Ready to submit the payment
* `in-progress` — Payment processing is underway
* `completed` — Payment completed; the order proceeds to delivery/completion
## Delivery statuses
* `awaiting-payment` — Delivery not started because payment is not yet complete
* `in-progress` — Delivery is being processed
* `failed` — Delivery failed; affected items will be refunded automatically
* `completed` — Delivery completed successfully
# Supporting Your Customers
Source: https://docs.crossmint.com/payments/headless/guides/supporting-your-customers
Support your customers facing issues with the checkout process
As an enterprise working with Crossmint, you receive priority customer support. Consequently, customer issues can be escalated to the Crossmint team.
## Information to pass onto Crossmint
When escalating customer issues to the Crossmint team, please ensure to pass on the following details, as it makes troubleshooting easier:
* **Email Address**: The email address they are using to purchase the digital asset.
* **Website URL**: The URL of the website they are trying to purchase the digital asset from.
* **Order Id**: The Order Id of the initiated payment.
* **Collection Id**: The Collection Id of the collection on Crossmint.
* **Project Id**: The Project Id of the project on Crossmint.
## Additional items to check
* You can review the **Orders tab** to check the status of the checkout.
* If you are the dev reporting the error, please share the sample code as well.
If nothing works and you are unable to troubleshoot the issue, you can redirect them to Crossmint's support
[here](https://help.crossmint.com/hc/en-us/requests/new/?utm_source=docs).
# Overview
Source: https://docs.crossmint.com/payments/headless/overview
Create fully custom checkout experiences
For general information about Crossmint's Payments product, see the [introduction](/payments/introduction). This guide will focus on the features specific to the headless checkout.
## When is the Headless Checkout the best fit?
* **You want to build the entire user experience**
* **You want to use Crossmint checkout outside of a browser environment** (e.g. native mobile app, game, VR headset, etc.)
* **You are okay spending more time developing your own UX**
## Introduction
Crossmint's headless checkout API is a set of REST APIs that allow you to integrate credit card and cross-chain crypto payments inside your app with full control of the user experience.
It is the most customizable version of the Crossmint suite of payments products, which allows anyone to buy digital assets with familiar payment methods (credit card, crypto on any chain independent on where the asset lives) with the same backend, but allowing full UI customizability and deep integration with your app's UX.
Wherever your imagination takes you...
## Get Started
Build a custom Pay with Crypto checkout experience for your digital assets
Build a custom credit-card checkout experience
Build a USDC checkout in 5 minutes (great for AI agents)
Sell memecoins quickly with a credit card checkout
Contact our sales team for advanced support.
## Advanced Topics
# Pay with Card - Memecoins
Source: https://docs.crossmint.com/payments/headless/quickstarts/credit-card-memecoin-cko
Create a fully customized memecoin checkout experience that accepts credit cards
## Introduction
This guide will show you how to accept credit card payments using Crossmint's Headless Checkout API for memecoin sales. You'll learn how to:
* Set up credit card payments for Solana memecoin purchases in JavaScript
* Implement a checkout UI
* Track order status and delivery
For a faster, embedded checkout solution with minimal setup time, see our [embedded memecoin
quickstart](/payments/embedded/quickstarts/credit-card-memecoin).
## Headless Memecoin Checkout
The headless checkout API allows complete control over your checkout experience, including:
* Custom UI components and styling
* Custom payment flow sequences
* Integrated analytics and tracking
* Custom error handling and retry logic
* Branded confirmation pages
### Create an Order
The first step in the headless checkout process is to create an order. An order is an object datastructure that represents an intent to purchase in Crossmint's systems. This guide will create a basic order, and then update it with required info step-by-step.
You can also create the entire order in one API call if the necessary information is available at the time of order
creation. This can be used for custom "one-click-checkout" experiences, should you wish to make them.
**Endpoint:** `POST` `https://www.crossmint.com/api/2022-06-09/orders`
Refer to the complete **create order** API reference [here](/api-reference/headless/create-order).
Memecoins are now testable in staging using the xmeme token (`7EivYFyNfgGj8xbUymR7J4LuxUHLKRzpLaERHLvi7Dgu`). All
other token purchases will fail in staging. For production launch with other tokens, contact the Crossmint sales team — production Checkout requires a signed Order Form and successful KYB. See the [production launch guide](/payments/headless/guides/production-launch) for the full sequence.
Use the JavaScript code snippet below to create a starting point for your order. Alternatively, use the API playground to explore and create your own order.
```javascript Node.js theme={null}
const apiKey = "your-server-api-key"; // CHANGE THIS TO YOUR SERVER API KEY
const tokenId = "solana:7EivYFyNfgGj8xbUymR7J4LuxUHLKRzpLaERHLvi7Dgu"; // xmeme token for staging
const deliveryAddress = "your-solana-wallet-address"; // CHANGE THIS TO YOUR RECEIVING SOLANA WALLET ADDRESS
const receiptEmail = "your-email@example.com"; // CHANGE THIS TO YOUR EMAIL
const options = {
method: "POST",
headers: {
"x-api-key": apiKey,
"Content-Type": "application/json",
},
body: JSON.stringify({
lineItems: [
{
tokenLocator: tokenId, // Token address in format solana:tokenAddress (e.g., solana:7EivYFyNfgGj8xbUymR7J4LuxUHLKRzpLaERHLvi7Dgu for xmeme token)
executionParameters: {
mode: "exact-in", // The execution method for the order. It tells Crossmint to operate in buying fungibles mode
amount: "1", // default currency USD
maxSlippageBps: "500", // Optional, or else default autogenerated slippage will be applied
},
},
],
payment: {
method: "card",
receiptEmail: receiptEmail,
},
recipient: {
walletAddress: deliveryAddress,
},
}),
};
fetch("https://staging.crossmint.com/api/2022-06-09/orders", options)
.then((response) => response.json())
.then((response) => console.log(JSON.stringify(response, null, 2)))
.catch((err) => console.error(err));
```
```json JSON theme={null}
{
"lineItems": [
{
"tokenLocator": "solana:tokenAddress",
"executionParameters": {
"mode": "exact-in",
"amount": "1",
"maxSlippageBps": "500"
}
}
],
"payment": {
"method": "card",
"receiptEmail": "receiptEmail"
},
"recipient": {
"walletAddress": "deliveryAddress"
}
}
```
For more details on tokenLocator formatting and other item selection options, see the [item selection page](/payments/headless/guides/item-selection#json-item-selection-examples).
```json theme={null}
{
"clientSecret": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJvcmRlcklkZW50aWZpZXIiOiJlZDM0YTU3OS03ZmJjLTQ1MDktYjhkOC05ZTYxOTU0Y2Q1NTUiLCJpYXQiOjE3Mzk0MDI3NjEsImV4cCI6MTczOTQ4OTE2MX0.8AU0Y31lJhnQD2-vAXEZp3ZeMSyh_Wdm9An02Z5AW0M",
"order": {
"orderId": "ed34a579-7fbc-4509-b8d8-9e61954cd555",
"phase": "payment",
"locale": "en-US",
"lineItems": [
{
"chain": "solana",
"metadata": {
"name": "xmeme",
"description": "Token xmeme from the contract: 7EivYFyNfgGj8xbUymR7J4LuxUHLKRzpLaERHLvi7Dgu",
"imageUrl": "https://arweave.net/VQrPjACwnQRmxdKBTqNwPiyo65x7LAT773t8Kd7YBzw"
},
"quote": {
"status": "valid",
"charges": {
"unit": {
"amount": "35.73",
"currency": "usd"
}
},
"totalPrice": {
"amount": "1",
"currency": "usd"
},
"quantityRange": {
"lowerBound": "0.0265905",
"upperBound": "0.0293895"
}
},
"delivery": {
"status": "awaiting-payment",
"recipient": {
"locator": "solana:BuWmGweapdysxU5VuUdi1RGoc4ibDG7TNirWjRtqF995",
"walletAddress": "your-solana-wallet-address"
}
},
"executionMode": "exact-in",
"maxSlippageBps": "500",
"executionParams": {
"mintHash": "7EivYFyNfgGj8xbUymR7J4LuxUHLKRzpLaERHLvi7Dgu",
"mode": "exact-in",
"amount": "1",
"maxSlippageBps": "500"
}
}
],
"quote": {
"status": "valid",
"quotedAt": "2025-02-12T23:26:00.397Z",
"expiresAt": "2025-02-12T23:26:30.397Z",
"totalPrice": {
"amount": "1",
"currency": "usd"
}
},
"payment": {
"status": "awaiting-payment",
"method": "basis-theory",
"currency": "usd",
"preparation": {},
"receiptEmail": "test@example.com"
}
}
}
```
Note the following parameters in the request body:
* `maxSlippageBps`: Optional, or else default autogenerated slippage will be applied
* `receiptEmail`: Optional. If the buyer pays with Apple Pay or Google Pay, Crossmint obtains the email automatically. Providing it improves fraud signal quality.
* `executionParameters.mode`: The execution method for the order. "exact-out" is for NFTs, "exact-in" is for fungible tokens
### Render the Crossmint Embedded Component
After creating an order, you'll need to render the Crossmint Embedded Component to collect payment information. This secure, embeddable UI component lets you accept payment methods, validates input, and handles errors:
```javascript theme={null}
import { CrossmintEmbeddedCheckout, CrossmintProvider } from "@crossmint/client-sdk-react-ui";
export function MyCheckoutPage({ clientSecret, order, recipientWalletAddress, receiptEmail }) {
return (
);
}
```
The `CrossmintEmbeddedCheckout` component is highly customizable. You can adjust colors, styling, layout, and behavior to match your brand. Learn more in our [UI customization guide](/payments/embedded/guides/ui-customization).
### Poll for Status Updates
After making the payment, you'll need to poll the [Get Order API](/api-reference/headless/get-order) to check on the delivery status and present this information to your user.
**Endpoint:** `GET` `https://staging.crossmint.com/api/2022-06-09/orders/`
Refer to the complete **get order** API reference [here](/api-reference/headless/get-order).
**Example Response:**
```json theme={null}
{
"id": "order_xyz",
"status": "completed",
"phases": {
"quote": { "status": "completed" },
"payment": { "status": "completed" },
"delivery": { "status": "completed", "details": "Memecoins delivered to specified wallet" }
}
}
```
### Handling Refunded Payments
When polling for order status, you may encounter a situation where `payment.status` is `completed` but the order also contains a `payment.refunded` property. This indicates that the payment was initially successful but has since been refunded.
```json theme={null}
{
"order": {
"payment": {
"status": "completed",
"refunded": {
"amount": "1.00",
"currency": "usd",
"txId": "0x1234abcd...",
"chain": "ethereum"
}
}
}
}
```
The `payment.refunded` object includes the following fields:
* `amount`: The amount that was refunded
* `currency`: The currency of the refund
* `txId`: The on-chain transaction ID the refund was sent in
* `chain`: The blockchain where the refund transaction occurred
When you encounter this state, your application should:
1. Display an appropriate message to the user indicating that their payment was refunded
2. Provide the transaction ID (`txId`) so users can verify the refund on-chain
3. Prevent any further actions related to the order (such as delivery expectations)
4. Provide options for the user to place a new order if desired
This state typically occurs when there was an issue with processing the order after payment was received, such as insufficient liquidity for memecoin purchases or compliance issues.
🎉 Congratulations! You've successfully set up your headless memecoin checkout. Check out the [Next Steps](#next-steps) section below to learn how to customize your integration.
## Next Steps
Learn how to design your headless checkout experience
Implement webhook handling for order updates
## Refreshing Orders
For memecoin checkout, the order expiration is 1 minute. The Crossmint Embedded Checkout will automatically refresh the order for you when the time comes. Alternatively, you can either create a new order or use the refresh quote API:
```javascript theme={null}
async refreshOrder(orderId, clientSecret) {
try {
const ancestorOrigins = typeof window !== 'undefined' && window.location?.ancestorOrigins
? Array.from(window.location.ancestorOrigins)
: [];
const response = await this.callApi(`2022-06-09/orders/${orderId}/refresh`, "POST", {}, {
"authorization": `${clientSecret}`,
"x-ancestor-origins": JSON.stringify(ancestorOrigins)
});
const parsed = await response.json();
return parsed;
} catch (error) {
console.error("Error refreshing quote:", error);
throw error;
}
}
```
# Pay with Card - NFTs
Source: https://docs.crossmint.com/payments/headless/quickstarts/credit-card-nft
Create a fully customized checkout experience with credit cards.
## Introduction
In this quickstart you'll learn how to accept credit card payments (including Apple and Google Pay) using Crossmint's headless APIs to purchase an NFT on the `polygon-amoy` testnet
## Prerequisites
## Integration Steps
### 1. Create an Order
```json theme={null}
{
"payment": {
"method": "card",
},
"lineItems": [
{
"collectionLocator": "crossmint:", // Use the collectionId you created in the Prerequisites section
"callData": {
"totalPrice": "0.0001"
}
}
]
}
```
```json theme={null}
{
"clientSecret": "_removed_",
"order": {
"orderId": "cc40e6af-f8dc-4ac2-8e26-d91a32f271cf",
"phase": "quote",
"locale": "en-US",
"lineItems": [
{
"chain": "polygon-amoy",
"quantity": 1,
"callData": {
"quantity": 1
},
"metadata": {
"name": "Headless Demo",
"description": "NFT Checkout Demo",
"imageUrl": "https://utfs.io/f/cd8545b7-3410-4fcd-988d-cd11951ed53c-g287ok.png"
},
"quote": {
"status": "requires-recipient",
"charges": {
"unit": {
"amount": "0.000169004",
"currency": "eth"
}
},
"totalPrice": {
"amount": "0.000169004",
"currency": "eth"
}
},
"delivery": {
"status": "awaiting-payment"
}
}
],
"quote": {
"status": "requires-recipient",
"quotedAt": "_DATE_",
"expiresAt": "_DATE_",
"totalPrice": {
"amount": "0.000169004",
"currency": "eth"
}
},
"payment": {
"status": "requires-quote",
"method": "basis-theory",
"preparation": {},
}
}
}
```
### 2. Update the Order with Recipient
Legally, for card payments, **an email *must* be specified** in order to deliver a receipt to your user, either in
`recipient.email` or in `payment.receiptEmail`. If one is not specified, the API will not return the fields
necessary to collect the user's payment.
```json theme={null}
{
"recipient": {
"email": "test@example.com"
}
}
```
```json theme={null}
{
"recipient": {
"walletAddress": "0xabcd1234..."
},
"payment": {
"method": "card", // When updating the payment, you must pass the complete object
"receiptEmail": "test@example.com"
}
}
```
### 3. Render the Crossmint Embedded Component
After creating an order, you'll need to render the Crossmint Embedded Component to collect payment information. This secure, embeddable UI component lets you accept payment methods, validates input, and handles errors:
```tsx theme={null}
import { CrossmintEmbeddedCheckout, CrossmintProvider } from "@crossmint/client-sdk-react-ui";
export function MyCheckoutPage({ clientSecret, order, recipientWalletAddress, receiptEmail }) {
return (
);
}
```
The `CrossmintEmbeddedCheckout` component is highly customizable. You can adjust colors, styling, layout, and behavior to match your brand. Learn more in our [UI customization guide](/payments/embedded/guides/ui-customization).
### 4. Poll for Status Updates
## Next Steps
# Pay with Other Crypto
Source: https://docs.crossmint.com/payments/headless/quickstarts/crypto
Create a fully customized checkout experience with crypto payments.
## Introduction
In this quickstart you'll learn how to accept multiple different cryptocurrencies as payment using Crossmint's headless APIs, to purchase an NFT on the `polygon-amoy` testnet.
## Prerequisites
If you are choosing to do this quickstart in your own console or IDE, consider you will need to have a way to prompt and sign crypto payments, like using Metamask.
### External Prerequisites
* Your crypto wallet of choice - Metamask, Coinbase Wallet, etc.
* A small balance of testnet currency in whichever cryptocurrency you would like to pay with
* [Ethereum, Base, Polygon](https://www.alchemy.com/faucets) - note that some of these faucets require a minimum of 0.1 ETH on mainnet, to prevent abuse
* [Solana devnet](https://solana.com/developers/guides/getstarted/solana-token-airdrop-and-faucets)
## Integration Steps
This quickstart will primarily focus on the API calls necessary to implement headless checkout in your project. You will also need to build out the frontend experience for your users.
### 1. Create an Order
```json theme={null}
{
"payment": {
"method": "base-sepolia",
"currency": "eth",
"payerAddress": "0xabcd1234..."
},
"lineItems": [
{
"collectionLocator": "crossmint:",
"callData": {
"totalPrice": "0.0001"
}
}
]
}
```
```json theme={null}
{
"payment": {
"method": "base-sepolia",
"currency": "usdc" | "degen" | "brett" | ... ,
"payerAddress": "0xabcd1234..."
},
"lineItems": [
{
"collectionLocator": "crossmint:",
"callData": {
"totalPrice": "0.0001"
}
}
]
}
```
```json theme={null}
{
"payment": {
"method": "solana",
"currency": "sol",
"payerAddress": "7NxpS1cMZ8..."
},
"lineItems": [
{
"collectionLocator": "crossmint:",
"callData": {
"totalPrice": "0.0001"
}
}
]
}
```
```json theme={null}
{
"payment": {
"method": "solana",
"currency": "usdc" | "bonk" | ... ,
"payerAddress": "7NxpS1cMZ8..."
},
"lineItems": [
{
"collectionLocator": "crossmint:",
"callData": {
"totalPrice": "0.0001"
}
}
]
}
```
```json theme={null}
{
"payment": {
"method": "polygon-amoy",
"currency": "matic",
"payerAddress": "0xabcd1234..."
},
"lineItems": [
{
"collectionLocator": "crossmint:",
"callData": {
"totalPrice": "0.0001"
}
}
]
}
```
```json theme={null}
{
"clientSecret": "_removed_",
"order": {
"orderId": "cc40e6af-f8dc-4ac2-8e26-d91a32f271cf",
"phase": "quote",
"locale": "en-US",
"lineItems": [
{
"chain": "polygon-amoy",
"quantity": 1,
"callData": {
"quantity": 1
},
"metadata": {
"name": "Headless Demo",
"description": "NFT Checkout Demo",
"imageUrl": "https://utfs.io/f/cd8545b7-3410-4fcd-988d-cd11951ed53c-g287ok.png"
},
"quote": {
"status": "requires-recipient",
"charges": {
"unit": {
"amount": "0.000169004",
"currency": "eth"
}
},
"totalPrice": {
"amount": "0.000169004",
"currency": "eth"
}
},
"delivery": {
"status": "awaiting-payment"
}
}
],
"quote": {
"status": "requires-recipient",
"quotedAt": "_DATE_",
"expiresAt": "_DATE_",
"totalPrice": {
"amount": "0.000169004",
"currency": "eth"
}
},
"payment": {
"status": "requires-quote"
}
}
}
```
In the requests above the `payerAddress` has been included, however if your user has not yet connected their wallet
you may omit this field, allow them to connect their wallet after creating the order, and submit the `payerAddress`
at a later time.
### 2. Update the Order with Recipient
```json theme={null}
{
"recipient": {
"email": "test@example.com"
}
}
```
```json theme={null}
{
"recipient": {
"walletAddress": "0xabcd1234..."
}
}
```
Notice you only need to pass the object that your are updating, in this case, `recipient`. You can also update
multiple properties in the same call. Available properties for update include `recipient`, `locale`, and the
`payment` object.
If specifying a recipient by wallet address, ensure the address is valid for the chain your **collection** is on,
which may differ from the chain the **payment is being performed** on.
```json theme={null}
{
"orderId": "cc40e6af-f8dc-4ac2-8e26-d91a32f271cf",
"phase": "payment",
"locale": "en-US",
"lineItems": [
{
"chain": "polygon-amoy",
"quantity": 1,
"callData": {
"quantity": 1
},
"metadata": {
"name": "Headless Demo",
"description": "NFT Checkout Demo",
"imageUrl": "https://utfs.io/f/cd8545b7-3410-4fcd-988d-cd11951ed53c-g287ok.png"
},
"quote": {
"status": "valid",
"charges": {
"unit": {
"amount": "0.0001681558",
"currency": "eth"
}
},
"totalPrice": {
"amount": "0.0001681558",
"currency": "eth"
}
},
"delivery": {
"status": "awaiting-payment"
}
}
],
"payment": {
"status": "awaiting-payment",
"preparation": {
"method": "crypto",
"chain": "base-sepolia",
"payerAddress": "0x6C3b3225759Cbda68F96378A9F0277B4374f9F06",
"serializedTransaction": "0x02f9012683aa36a744848917370085080992ce6c82629c94a105c311fa72b8fb78c992ecbdb8b02ea5bd394d869a9d359ca000b8f465794a68624763694f694a49557a49314e694973496e523563434936496b705856434a392e65794a756232356a5a534936496a4d3559544e6d4d444d334c544d7a4e6d51744e4441305a5331684d6a45774c575268593249335957526b5a4456694e794973496d39795a4756795357526c626e52705a6d6c6c63694936496d4e6a4e44426c4e6d466d4c5759345a474d744e47466a4d6930345a5449324c5751354d57457a4d6d59794e7a466a5a694973496d6c68644349364d5463784e546b334f54557a4e33302e662d4949646c653375314c58634c7845765438754e336d74786370764a686f31384a5a4b76483768456659c0"
}
}
}
```
Notice at the bottom, the new `serializedTransaction` property. This is a unique identifier let's Crossmint know exactly which order you are updating.
Anytime you update an order you ***must*** use the returned `serializedTransaction` property.
### 3. Show Purchase Preview to User
Now, after you've told Crossmint systems what you want, and you have passed a valid place to send the NFT, Crossmint will return a quote with prices for the desired items
Within the returned response, you'll find the `lineItems` array which contains the metadata and a quote for each line item. Within a quote, a detailed breakdown of its charges can be found.
If you are developing in an IDE or in terminal, the returned `lineitems` may be stuck in an \[Object], or be in a
hard-to-understand object format. You can make this human-readable for yourself by adding `JSON.stringify(response)`
```jsx theme={null}
```
```json theme={null}
{
// some properties removed for brevity
"order": {
"lineItems": [
{
"chain": "polygon-amoy",
"quantity": 1,
"callData": {
"quantity": 1
},
"metadata": {
"name": "Headless Demo",
"description": "NFT Checkout Demo",
"imageUrl": "https://utfs.io/f/cd8545b7-3410-4fcd-988d-cd11951ed53c-g287ok.png"
},
"quote": {
"status": "valid",
"charges": {
"unit": {
"amount": "0.000169004",
"currency": "eth"
}
},
"totalPrice": {
"amount": "0.000169004",
"currency": "eth"
}
},
"delivery": {
"status": "awaiting-payment"
}
}
]
}
}
```
### 4. Request Payment from User
You now need to request crypto payment from the user.
You will use the `serializedTransaction` returned in the purchase preview from the previous step. The `serializedTransaction` needs to be structured into a transaction object, which can then be passed to a crypto payment prompter, like viem's `sendTransaction` or wagmi's `sendTransactionAsync`.
Whichever method you use, it should open the user's default wallet extension in the browser and allow the user to complete the cross-chain payment.
If you've followed along this far you can simply paste the `serializedTransaction` property returned in your
previous API call into the app below. This will allow you to sign and send the transaction.
Ensure your wallet is connected to the **devnet** network, otherwise your funds may be lost!
```tsx theme={null}
"use client";
import { ConnectButton } from "@rainbow-me/rainbowkit";
import { useState } from "react";
import { parseTransaction } from "viem"; // Refer to their documentation details to make viem work for your needs
import { useSendTransaction } from "wagmi"; // Refer to their documentation details to make wagmi work for your needs
export default function Home() {
const { data: hash, isPending, sendTransactionAsync } = useSendTransaction();
const signAndSendTransaction = async () => {
const serializedTxn = "0x_your_serialized_transaction_response";
const txn = parseTransaction(serializedTxn || "0x");
await sendTransactionAsync({
to: txn.to as `0x${string}`,
value: BigInt(txn.value ? txn.value.toString() : "0"),
data: txn.data as `0x${string}`,
chainId: txn.chainId,
});
};
return ;
}
```
For a deeper example, refer to the repository linked below, which is the exact code running in the App tab.
Refer to the full repo for complete code examples
### 5. Poll for Status Updates
## Next Steps
# Pay with USDC
Source: https://docs.crossmint.com/payments/headless/quickstarts/paying-usdc
Buy or sell any tokenized asset with USDC, in an API call
## Introduction
This guide will show you how to quickly buy or sell any tokenized asset (NFT) with USDC, using Crossmint's headless APIs.
## Prerequisites
## External prerequisites
Crypto wallet supporting USDC - [Metamask](https://metamask.io/), [Phantom](https://phantom.app/), [Coinbase Wallet](https://wallet.coinbase.com), etc.
A small balance of **USDC** testnet currency in your wallet on the **Base-sepolia** network.
Get testnet USDC here:
* [Crossmint USDC Faucet (Base-sepolia)](https://usdc-faucet.vercel.app/)
The above faucet provides both USDC and USDXM. This quickstart uses USDC by default, but you can also use USDXM (see contract address below).
USDXM is a test token that represents USDC. It has the same behavior as USDC. In production, you'll use real USDC instead.
**Adding USDC to MetaMask:**
* Import the token to MetaMask:
1. Open MetaMask and click "Import tokens"
2. For USDC: Use the official USDC contract address from Circle `0x036CbD53842c5426634e7929541eC2318f3dCF7e`
3. For USDXM: Use the USDXM contract address `0x14196F08a4Fa0B66B7331bC40dd6bCd8A1dEeA9F`
4. Click Import
5. Refresh your wallet
## Create an order and pay with USDC
Clone the quickstart repo:
```bash theme={null}
git clone https://github.com/Crossmint/headless-pay-usdc.git
```
Navigate to the project directory:
```bash theme={null}
cd headless-pay-usdc
```
Install the dependencies:
```bash theme={null}
pnpm i
```
Set the following keys in the `.env` file:
```bash theme={null}
CROSSMINT_API_KEY=""
COLLECTION_ID=""
PAYER_ADDRESS=""
EMAIL_ADDRESS=""
```
* `CROSSMINT_API_KEY`, `COLLECTION_ID` are found from the [Developer Console](https://staging.crossmint.com/console/). See [Prerequisites](#prerequisites)
* `PAYER_ADDRESS` is your wallet address that contains USDC.
* `EMAIL_ADDRESS` is where you'll receive your NFT in a new Crossmint wallet that you can access later.
Run the application:
```bash theme={null}
pnpm dev
```
This will:
1. Create an order that is waiting for a **USDC** payment on the **Base-sepolia** network
2. Return the **serializedTransaction**, which will be used in the next step
3. Poll the status of the order until it is complete
1. Copy the `serializedTransaction` returned in the previous step and paste it below
2. Connect to your USDC wallet that was specified in the `.env` file
3. Send the transaction
The code will poll for the status of the order until it is marked as complete.
You should see it changing from "payment" to "delivery" and finally to "completed".
You will also see the final order details, including the transaction hash, in your terminal.
## Access your NFT
Once the order is complete, you will receive an email with a link to the purchased item, which can be viewed in the Crossmint website.
You can also access your NFT by logging in to your Crossmint wallet at
[crossmint.com](https://staging.crossmint.com/user/collection) with the email address you specified in the `.env`
file.
## Understanding the code
### Creating an order
The order creation process involves sending a POST request to Crossmint's API with the necessary payment and recipient details. Let's break down the key components:
```ts src/createOrder.ts theme={null}
import { API_KEY, API_URL, COLLECTION_ID, EMAIL_ADDRESS, PAYER_ADDRESS } from "./utils";
export async function createOrder() {
const payload = {
recipient: {
email: EMAIL_ADDRESS, // Replace with actual recipient wallet address
},
payment: {
method: "base-sepolia",
currency: "usdc",
payerAddress: PAYER_ADDRESS,
},
lineItems: [
{
collectionLocator: `crossmint:${COLLECTION_ID}`
},
],
};
...
}
```
#### Key parameters explained
* **recipient**: Specifies where to deliver the tokenized asset
* `email`: The email address where the tokenized asset will be delivered in a Crossmint wallet.
Instead of an email, a `walletAddress` field can be used to deliver the asset to a specific wallet address.
* **payment**: Defines how the payment will be made
* `method`: The blockchain network (`base-sepolia` for testnet)
* `currency`: The payment currency (`usdc`)
* `payerAddress`: The wallet address that will send the USDC payment
* **lineItems**: Specifies what is being purchased
* `collectionLocator`: Identifies the collection
#### API request
```ts src/createOrder.ts theme={null}
const response = await fetch(`${API_URL}/api/2022-06-09/orders`, {
method: "POST",
headers: {
"Content-Type": "application/json",
"x-api-key": API_KEY,
},
body: JSON.stringify(payload),
});
```
The API request:
* Sends a POST request to Crossmint's order endpoint
* Includes your API key for authentication
* Sends the order data as JSON in the request body
#### Response handling
```ts src/createOrder.ts theme={null}
if (!response.ok) {
throw new Error(`Failed to create order: ${response.status} ${response.statusText}`);
}
const jsonResponse = await response.json();
return {
"clientSecret": jsonResponse["clientSecret"], // Used for order authentication
"order": jsonResponse["order"] // Contains the order details
}
```
The response includes:
* `clientSecret`: A unique token used for authenticating subsequent requests for this order
* `order`: The order details including the order ID and status
For more details on creating orders via the headless API, see the [Create Order API Reference](/api-reference/headless/create-order).
### Polling an order
The order status polling process involves periodically checking the order's status until it's completed. Let's examine how this works:
```ts src/getOrder.ts theme={null}
import { API_KEY, API_URL } from "./utils";
export async function getOrder(orderId: string) {
const response = await fetch(`${API_URL}/api/2022-06-09/orders/${orderId}`, {
method: "GET",
headers: {
"Content-Type": "application/json",
"x-api-key": API_KEY, // API authentication
}
});
if (!response.ok) {
throw new Error(`Failed to create order: ${response.status} ${response.statusText}`);
}
return response.json();
}
```
#### Get order function explained
* Takes `orderId` as parameter
* Makes a GET request to fetch the current state of the order
* Uses API key for authentication
* Returns the order details as JSON
```ts src/getOrder.ts theme={null}
export async function pollOrder(orderId: string) {
while (true) {
const order = await getOrder(orderId);
console.log(`Current order status: \x1b[33m${order.phase}\x1b[0m`);
if (order.phase === "completed") {
return order;
}
await new Promise(resolve => setTimeout(resolve, 3000));
}
}
```
#### Poll order function explained
* Continuously checks the order status every 3 seconds.
* Prints the current phase of the order
* Possible order phases include:
* `payment`: Waiting for or processing payment
* `delivery`: NFT is being delivered
* `completed`: Order is successfully completed
* Returns the final order details when completed
The 3-second polling interval helps prevent rate limiting while still providing timely updates. Adjust this value based on your needs.
For more details on polling for order status, see the [Get Order API Reference](/api-reference/headless/get-order).
Always implement proper error handling, retry mechanisms, and asynchronous polling in production environments to
handle network issues or API interruptions.
# Overview
Source: https://docs.crossmint.com/payments/introduction
Allow your users to buy onchain assets using any payment method
Crossmint enables seamless in-app checkout experiences that minimize onboarding friction and maximize conversion.
With Checkout, users can buy memecoins, closed-loop tokens, NFTs, and other assets with debit cards, credit cards, Apple Pay, Google Pay, and stablecoins. Learn more about the types of assets that can be supported here.
Checkout works across all non-sanctioned geographies, requires no KYC from buyers, includes built-in chargeback protection, and has market-leading approval rates. Sellers complete KYB once during onboarding to enable production access — see [account verification](/introduction/platform/account-verification).
## See It in Action
The video below showcases Embedded Checkout for memecoins on fomo, highlighting a seamless mobile onboarding experience with Apple Pay.
## Key Features
Support for any non-sanctioned geo.
No ID checks during onboarding.
Compared to 55% industry benchmark.
Crossmint handles disputed transactions.
Dedicated team of anti-fraud experts.
Tools to measure and troubleshoot faster.
## Ways to Integrate
Crossmint-hosted pop-up.
Embedded iframe with customizable UI.
Buy or sell via API, ideal for agents or custom UIs
Crossmint generally recommends to use Embedded Checkout. It yields experiences that feel completely native while handling PSP orchestration and error management, and offering better approval rates.
## Get started
## FAQs
The *checkout* is used when a user is buying a specific item — like an NFT, memecoin, or product — with fiat or crypto. It's a direct purchase of a specific item.
The *onramp* is used when a user wants to fund their wallet with crypto — without buying a specific item. Think of it as topping up a balance instead of making a purchase.
Use *checkout* when selling something. Use *onramp* when your users need crypto to spend later.
The checkout has no KYC, is available on any region non-sanctioned by OFAC, and offers the best UX and conversion rate. The onramp requires KYC and some geographical limitations apply.
Embedded is the best solution for most teams. It offers embeddable iframe / mobile components which feel native and allow for customizability. Integration takes minutes, as the SDK orchestrates all integrations with payment processors, different states, and errors.
Our team continuously monitors market activity and proactively whitelists eligible memecoins across all of our partners. As of the last update, the Checkout supported memecoins representing around \~80% of the memecoin trading volume in the previous 24 hours.
You may also request reviews for specific tokens at any time. Review timelines and throughput depend on your SaaS plan and request volume, and we'll always do our best to accommodate. If requests become too frequent relative to transaction volume, we may ask you to upgrade to a higher plan to continue supporting custom review requests.
Crossmint settles payments instantly with sellers directly via the smart contract, in the native currency specified
on the contract. Assets are sent directly into the wallet of the buyer, without intermediation by Crossmint.
You don't have to worry about chargeback risk. Crossmint will take on the risk and manage disputes directly with the bank.
### With Crossmint's contracts
Managed Collections have two options for fees:
**1. Buyer pays fees** - The buyer (your customer) pays the price of the asset you set in console, plus applicable fees. The Seller (you, the creator) receives the price set in console (aka *the payout*).
**2. Seller pays fees** - The buyer pays the exact price of the asset set in console. You receive the set asset price, minus applicable fees.
To access this setting in the Crossmint Console, go to your managed collection, click on "Payments" and then "Settings".
Here you will find the "Who pays the fees?" switch, which allows you to set the payer of fees for your collection. You can update and save this setting at any time.
* The total fees per transaction are the same in either option.
* Managed collections are Buyer pays by default.
### With imported contracts
Imported collections are only compatible with the "Buyer pays fees" option.
Crossmint places an authorization hold on the user's card before initiating the transaction. Funds are only captured once the transaction is successfully completed. If the transaction fails, the hold is released and the user is not charged.
Crossmint supports primary and secondary sales for closed-loop tokens and [NFTs](/payments/advanced/marketplaces-and-launchpads). For memecoins, Crossmint only supports secondary sales.
For more information, contact sales.
Staging is self-serve — create a staging console account and start building immediately.
Production access requires a signed Order Form and completed KYB verification. Contact sales to get started. See the [account verification documentation](/introduction/platform/account-verification) for the full sequence.
# Localization
Source: https://docs.crossmint.com/payments/pay-button/customize/localization
Customize the language and currency displayed on checkout
## How to Adjust the Currency and Language
To customize the language use the property `locale` and for currency, set `defaultCurrency`. Here's an example:
```tsx {3,16} theme={null}
```
# React Hooks
Source: https://docs.crossmint.com/payments/pay-button/guides/hooks
Learn how to use React hooks to control and customize the Hosted Checkout UI
## Complete Example
Here's a full example showing how to implement a custom checkout experience:
```tsx theme={null}
import {
CrossmintProvider,
CrossmintCheckoutProvider,
CrossmintHostedCheckout,
useCrossmintCheckout,
} from "@crossmint/client-sdk-react-ui";
function CheckoutPage() {
return (
);
}
function CheckoutStatus() {
const { order } = useCrossmintCheckout();
if (!order) {
return
Loading...
;
}
switch (order.phase) {
case "completed":
return
Purchase complete!
;
case "delivery":
return
Delivering your NFTs...
;
case "payment":
return
Processing payment...
;
case "quote":
return
Preparing your order...
;
}
}
```
## Related Resources
* [UI Customization](/payments/pay-button/guides/ui-customization)
* [Payment Methods](/payments/pay-button/guides/payment-methods)
* [Order Lifecycle](/payments/headless/guides/order-lifecycle)
* [Testing Tips](/payments/advanced/testing-tips)
# Item Selection
Source: https://docs.crossmint.com/payments/pay-button/guides/item-selection
Specify which items to purchase with Hosted Checkout
The Hosted Checkout (Pay Button) allows you to specify which items your users can purchase using the `lineItems` property. This guide explains how to configure item selection for different asset types.
## Item Selection Examples
```tsx collectionLocator theme={null}
```
```tsx collectionLocator - imported contract theme={null}
```
```tsx Solana theme={null}
```
```tsx tokenLocator - EVM theme={null}
::
callData: {
totalPrice: "25.00", // Only totalPrice is required for token purchases
// For external EVM contracts, you might need additional parameters:
// _amount: 1, // if your contract uses _amount instead of quantity
// tokenId: "1234", // if your contract requires explicit tokenId
// metadata: "ipfs://...", // if your contract accepts metadata
},
}}
/>
```
```tsx tokenLocator - Solana theme={null}
:
callData: {
totalPrice: "25.00",
buyerCreatorRoyaltyPercent: 100, // Required for Solana
},
}}
/>
```
```tsx productLocator theme={null}
```
## Order Status Tracking
The Pay Button cannot use webhooks for order status tracking because it operates in a separate window. Therefore, you'll need to implement polling to check order status through alternative methods. See the [Order Status Guide](/payments/pay-button/guides/order-status) for more information on tracking order completion in pay button.
# Order Status
Source: https://docs.crossmint.com/payments/pay-button/guides/order-status
Track order status with Hosted Checkout
Since Hosted Checkout opens in a separate window, you can't use `useCrossmintCheckout` to track order status. Instead, you have three options:
## 1. Using Callback Functions
```tsx theme={null}
{
// Handle successful order
console.log("Order completed:", orderId);
}}
onFailure={(error) => {
// Handle failed order
console.error("Order failed:", error);
}}
/>
```
## 2. Using Event Listeners
```tsx theme={null}
useEffect(() => {
const handleMessage = (event: MessageEvent) => {
if (event.data.type === "crossmint:checkout:completed") {
console.log("Order completed:", event.data.orderId);
}
};
window.addEventListener("message", handleMessage);
return () => window.removeEventListener("message", handleMessage);
}, []);
```
## 3. Using Order Status API
For real-time order status updates, you can use the [Order Status API](/payments/pay-button/guides/order-status) to check the status of an order programmatically.
# Configure Payment Options
Source: https://docs.crossmint.com/payments/pay-button/guides/payment-methods
Customize the payment methods available to users
## Payment Methods Supported
Buyers can purchase digital assets with any cryptocurrency. For example, they can pay for an asset on Optimism chain
with mainnet ETH. This is done real time without bridging and lower gas fees.
## Configure Payment Settings
You can configure payment preferences using the `payment` prop:
```tsx theme={null}
```
### Fiat Payment Options
Configure fiat payment settings:
```tsx theme={null}
payment={{
fiat: {
enabled: true,
defaultCurrency: "usd"
}
}}
```
### Crypto Payment Options
Configure crypto payment preferences:
```tsx theme={null}
payment={{
crypto: {
enabled: true,
defaultChain: "polygon", // Set preferred blockchain
defaultCurrency: "matic" // Set preferred crypto currency
}
}}
```
You will be paid in the currency expected by your registered mint function, regardless of how the buyer pays.
# Production Launch
Source: https://docs.crossmint.com/payments/pay-button/guides/production-launch
Follow this checklist to ensure you are ready for production launch with Pay Button
## From Staging to Production with Pay Button
***
### Launch Checklist
1. Change your API keys to the production versions. Ensure your production API key has the appropriate scopes enabled, such as `orders.create`.
2. Update your collection ID to the production one.
3. As needed, change your passed-in props to be production ready (e.g. make `email`, `payment`, `lineItems` programmatically filled).
```jsx {(1, 4)} Production theme={null}
```
```jsx {(1, 4)} Staging theme={null}
```
# Specify Recipient
Source: https://docs.crossmint.com/payments/pay-button/guides/specify-recipient
Select where purchased items should be delivered in Hosted Checkout
## Specifying the Recipient
For Hosted Checkout, you specify the recipient by adding a `recipient` object to your checkout component. The recipient can be specified by email, wallet address, or physical address for physical products.
### Recipient by Email
When specifying a recipient by email, Crossmint will automatically create a secure custodial wallet on the fly for that email address:
```jsx React {3-5} theme={null}
Buy with Crossmint
```
The recipient will be able to access their purchased items by logging into their Crossmint wallet in [staging](https://staging.crossmint.com/user/collection) or [mainnet](https://www.crossmint.com/user/collection).
### Recipient by Wallet Address
To deliver items directly to a specific blockchain wallet address:
```jsx React {3-6} theme={null}
```
### Physical Product Recipients
When purchasing physical products, you can specify a physical address for delivery:
```jsx React {3-13} theme={null}
```
# Customize the Button UI
Source: https://docs.crossmint.com/payments/pay-button/guides/ui-customization
Modify the payment button and checkout experience to match your website's style
You can customize both the payment button and the checkout modal appearance using the `appearance` prop.
## Button Customization
### Theme Options
The button can be styled using predefined themes:
```tsx theme={null}
```
### Custom Colors
Customize the button's accent color:
```tsx theme={null}
```
## Checkout Experience Customization
### Display Mode
Choose how the checkout appears:
```tsx theme={null}
```
### Theme
Set the checkout interface theme:
```tsx theme={null}
```
### Overlay Options
Control the modal overlay behavior:
```tsx theme={null}
```
## Complete Styling Example
Here's an example combining multiple customization options:
```tsx theme={null}
```
The appearance configuration allows you to: - Customize button and checkout themes independently - Set custom accent
colors - Control how the checkout displays (popup/tab) - Manage overlay behavior
# Overview
Source: https://docs.crossmint.com/payments/pay-button/overview
Add a button to your site which opens the checkout in a pop-up or new tab
The hosted variant of checkout is only available for React applications. If you're using a different framework like
Vite or React Native, you can use the [Headless version](/payments/headless/overview) instead.
For general information about Crossmint's Payments product, see the [introduction](/payments/introduction). This guide
will focus on the features specific to the Hosted modality.
## When is Hosted Checkout the best fit?
* **You want to launch quickly and don't need deep customization or a fully native user experience**.
## Get Started
Start selling digital assets in 5 minutes.
Contact our sales team for advanced support.
## Advanced Topics
# Quickstart ⚡
Source: https://docs.crossmint.com/payments/pay-button/quickstart
Accept payments in under 5 min by just adding a button to your frontend
## Introduction
In this guide, you will create a web app with Next.js which allows customers to buy digital assets with credit card and
crypto payments using Crossmint's hosted checkout. We will add a button that opens a checkout hosted in a pop-up or new
tab.
### Basic Integration
This quickstart guide focuses on NFT Collections/Drops (primary sales), but Crossmint also supports marketplace integrations.
Create `.env.local` in your project root:
```sh theme={null}
NEXT_PUBLIC_CLIENT_API_KEY="_YOUR_CLIENT_API_KEY_" # From API Keys page
NEXT_PUBLIC_COLLECTION_ID="_YOUR_COLLECTION_ID_" # From Collection details page
```
Create `/src/app/page.tsx` with:
```tsx theme={null}
"use client";
import { CrossmintProvider, CrossmintHostedCheckout } from "@crossmint/client-sdk-react-ui";
export default function Home() {
const clientApiKey = process.env.NEXT_PUBLIC_CLIENT_API_KEY as string;
const collectionId = process.env.NEXT_PUBLIC_COLLECTION_ID as string;
return (
);
}
```
Navigate to the directory your package.json is to run the app
```bash theme={null}
cd hosted-checkout/crossmint-hosted-checkout-demo
```
### (Optional) Advanced Integration
Need purchase tracking, multiple line items, or more customization? Here's a complete setup with additional features:
```tsx theme={null}
"use client";
import { useEffect } from "react";
import {
CrossmintProvider,
CrossmintCheckoutProvider,
CrossmintHostedCheckout,
useCrossmintCheckout,
} from "@crossmint/client-sdk-react-ui";
// Component with purchase tracking
function Checkout() {
const { order } = useCrossmintCheckout();
const collectionId = process.env.NEXT_PUBLIC_COLLECTION_ID as string;
useEffect(() => {
if (order && order.phase === "completed") {
console.log("Purchase completed!");
// Handle successful purchase
}
}, [order]);
return (
);
}
// Main component with providers
export default function Home() {
const clientApiKey = process.env.NEXT_PUBLIC_CLIENT_API_KEY as string;
return (
);
}
```
```tsx theme={null}
"use client";
import { useEffect } from "react";
import {
CrossmintProvider,
CrossmintCheckoutProvider,
CrossmintHostedCheckout,
useCrossmintCheckout,
} from "@crossmint/client-sdk-react-ui";
// Component with purchase tracking
function Checkout() {
const { order } = useCrossmintCheckout();
useEffect(() => {
if (order && order.phase === "completed") {
console.log("Purchase completed!");
// Handle successful purchase
}
}, [order]);
return (
);
}
// Main component with providers
export default function Home() {
const clientApiKey = process.env.NEXT_PUBLIC_CLIENT_API_KEY as string;
return (
);
}
```
This advanced example showcases: - Multiple NFTs in one checkout - Purchase status tracking - Preferred payment
methods and currencies - Email-based digital asset delivery - Language settings Learn more in our guides: - [Payment
Methods](/payments/pay-button/guides/payment-methods) - [Multi-purchases](/payments/pay-button/guides/item-selection#multiple-item-orders)
* [React Hooks](/payments/pay-button/guides/hooks)
### Testing Your Integration
This demo uses the staging environment:
* Use [test credit cards](/payments/advanced/testing-tips#test-credit-card-numbers) for payments
* Get test USDC from our [faucet](/payments/advanced/testing-tips#getting-test-tokens)
* Check [price limits](/payments/advanced/testing-tips#limits-in-staging) for staging
## All Set!
You've successfully integrated a hosted checkout with credit card and crypto payment methods!
Remember this demo is built on staging, so the digital assets will show up on the testnets. To launch on production,
check the [production launch checklist](/payments/advanced/production-launch). You will need to contact
[Sales](https://www.crossmint.com/contact/sales) to enable the embedded checkout on production.
## Next Steps
### Customize the UI and Behavior Further
Learn how to customize the look and feel of the checkout button
Configure available payment options for your users
Use React hooks to build custom checkout experiences
### Advanced Topics
# Plan Your Integration
Source: https://docs.crossmint.com/payments/plan-your-solution
Walk through key decisions to design your Crossmint Checkout integration
Crossmint Checkout supports multiple asset types and integration modalities. This guide helps you make key decisions for your integration.
## 1. Select the asset type you're selling
Learn more about the types of assets that can be supported here.
## 2. Choose an integration modality
Crossmint checkout can be integrated into your application in three different ways, ranging from a bare metal headless
API with maximum UI flexibility, to a low-code hosted solution.
| Modality | Description | Integration Complexity | Supported Platforms | Recommended For |
| :------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------- | :------------------------ | :------------------ | :-------------------------------------------------------------------------------------- |
| [Headless](/payments/headless/overview) | Buy or sell using a REST API. Puts additional work on your side. | \~500 Lines of Code (LOC) | Everywhere | Best suited for backend flows and platforms not natively supported by other modalities. |
| [Embedded (strongly recommended)](/payments/embedded/overview) | Embed a checkout natively within your app. Handles orchestration, errors, and has better approval rates. | \~100 LOC | React, WebView | E-commerce sites, token marketplaces, embedded finance |
| [Hosted](/payments/pay-button/overview) | Add a button to your app that opens a checkout in a separate tab or window. | \~10 LOC | Web | One-off integrations for an event, quick prototypes |
Embedded Checkout is generally recommended. It yields experiences that feel completely native while handling PSP orchestration and error management, and offering better approval rates.
## 3. Choose payment method(s)
You can choose multiple options from the following payment methods to include. Consider the following as you decide:
* Is your audience crypto-native or mainstream consumers familiar with fiat payments?
* What level of optionality do you want to enable? Adding many methods may add complexity during a user's checkout flow.
| Payment Method | Description | Best For |
| :----------------------- | :------------------------------------------------------------------------------- | :----------------------------------------- |
| **Debit & Credit Cards** | All major credit cards supported - Visa, Mastercard, American Express, and more. | Mainstream consumers, global accessibility |
| **Apple & Google Pay** | Check out in under 30 seconds with faster payment methods. | Mobile-first users, quick transactions |
| **Stablecoins** | Pay with USDC, USDT, and other stablecoins for lower transaction fees. | Crypto-native users, lower fees |
| **Cross-Chain Crypto** | Pay with any token, on any chain. | Multi-chain users, existing crypto holders |
Stablecoins and cross-chain crypto not supported for memecoin checkout.
## 4. (For NFTs only) Do you need to create or import an existing collection?
**Choose one:** Do you want to create a new NFT collection or import an existing one?
Use our templates in the Console
Connect your deployed smart contract
## Get Started
Launch a Crossmint-hosted checkout page in a pop-up
Import Crossmint checkout components in your application
Buy or sell via an API, best suited for agents or deep UI customization
# Get Started
Source: https://docs.crossmint.com/sdk-reference/auth/react-native/get-started
Installation and setup for the React Native SDK reference for Crossmint authentication
### Latest React Native SDK version -
The Crossmint React Native SDK (`@crossmint/client-sdk-react-native-ui`) provides React Native providers and hooks for integrating Crossmint authentication into your mobile application, including email OTP, social login (OAuth), and session management.
## Installation
## Provider Setup
Wrap your application with the required providers:
```tsx theme={null}
import {
CrossmintProvider,
CrossmintAuthProvider,
} from "@crossmint/client-sdk-react-native-ui";
function App() {
return (
{/* Your app components */}
);
}
```
## Quick Example
Once providers are set up, use the `useCrossmintAuth` hook to access auth state:
```tsx theme={null}
import { useCrossmintAuth } from "@crossmint/client-sdk-react-native-ui";
import { View, Text, TouchableOpacity } from "react-native";
function LoginScreen() {
const { loginWithOAuth, logout, user, status } = useCrossmintAuth();
if (status === "initializing") return Loading...;
if (user == null) {
return (
loginWithOAuth("google")}>
Sign in with Google
);
}
return (
Welcome, {user.email}Log out
);
}
```
## Next Steps
* [Providers](/sdk-reference/auth/react-native/providers) — Configure providers and their options
* [Hooks](/sdk-reference/auth/react-native/hooks) — Access SDK state via React hooks
# Hooks
Source: https://docs.crossmint.com/sdk-reference/auth/react-native/hooks
React Native hooks for React Native SDK reference for Crossmint authentication
## useCrossmintAuth()
Access Crossmint authentication state and actions: `loginWithOAuth`, `logout`, the current `user`, `jwt`, and auth `status`. Must be used within a `CrossmintAuthProvider`.
### Usage
```tsx theme={null}
import { useCrossmintAuth } from "@crossmint/client-sdk-react-native-ui";
import { View, Text, TouchableOpacity } from "react-native";
function AuthStatus() {
const { loginWithOAuth, logout, user, jwt, status } = useCrossmintAuth();
if (status === "initializing") return Loading...;
return (
{user == null ? (
loginWithOAuth("google")}>
Log in
) : (
Log out ({user.email})
)}
);
}
```
> **Note:** Must be used within a CrossmintAuthProvider.
# Providers
Source: https://docs.crossmint.com/sdk-reference/auth/react-native/providers
React Native context providers for React Native SDK reference for Crossmint authentication
1. `CrossmintProvider` — SDK initialization (required for all Crossmint features)
2. `CrossmintAuthProvider` — Authentication state, login/logout, and the auth flow
## CrossmintProvider
Root provider for the Crossmint SDK. Must wrap your entire application. Initializes the SDK with your API key and sets up logging.
### Props
Your Crossmint client-side API key.
Application identifier, sent as `x-app-identifier` header.
Minimum log level for console output (or "silent" to suppress all output). Logs below this level will not be written to the console. Set to "silent" to completely suppress console output. Defaults to "debug" (all logs shown) for backward compatibility.
Override the base API URL.
### Usage
```tsx theme={null}
import { CrossmintProvider } from "@crossmint/client-sdk-react-native-ui";
function App() {
return (
{/* Your app content */}
);
}
```
***
## CrossmintAuthProvider
Provides Crossmint authentication to your React Native app: social login (OAuth) and session management. Exposes auth state via the `useCrossmintAuth` hook and stores auth material in secure device storage. Must be nested inside `CrossmintProvider`.
### Props
Your app's deep link scheme(s) (e.g. `"myapp://"`), used for OAuth redirects back into your app.
Custom title for the auth modal.
Custom route for logging out, for server-side session management.
Callback invoked when the user successfully logs in.
Whether to prefetch OAuth provider URLs when the provider mounts.
Custom route for refreshing the auth token, for server-side session management.
Custom storage provider for auth material. Defaults to browser cookies on web and secure device storage on React Native.
### Usage
```tsx theme={null}
import {
CrossmintProvider,
CrossmintAuthProvider,
} from "@crossmint/client-sdk-react-native-ui";
function App() {
return (
{/* Your app content */}
);
}
```
> **Note:** CrossmintAuthProvider must be nested inside CrossmintProvider. Set appSchema to your app's deep link scheme so OAuth redirects return to your app.
# Components
Source: https://docs.crossmint.com/sdk-reference/auth/react/components
React components for React SDK reference for Crossmint authentication
## EmbeddedAuthForm
Renders the Crossmint auth form inline in your page instead of in a modal. Displays the same login methods configured on `CrossmintAuthProvider`, which it must be rendered inside of.
### Usage
```tsx theme={null}
import { EmbeddedAuthForm, useCrossmintAuth } from "@crossmint/client-sdk-react-ui";
function LoginPage() {
const { status } = useCrossmintAuth();
if (status === "logged-in") {
return
You are logged in!
;
}
return (
);
}
```
> **Note:** Must be rendered inside CrossmintAuthProvider. Renders the same auth form shown in the login modal, but inline in your page.
# Get Started
Source: https://docs.crossmint.com/sdk-reference/auth/react/get-started
Installation and setup for the React SDK reference for Crossmint authentication
### Latest React SDK version -
The Crossmint React SDK (`@crossmint/client-sdk-react-ui`) provides React components and hooks for integrating Crossmint authentication into your application, including email OTP, social login (OAuth), and session management.
## Installation
## Provider Setup
Wrap your application with the required providers:
```tsx theme={null}
import {
CrossmintProvider,
CrossmintAuthProvider,
} from "@crossmint/client-sdk-react-ui";
function App() {
return (
{/* Your app content */}
);
}
```
> **Note:** CrossmintAuthProvider must be nested inside CrossmintProvider.
## Quick Example
Once providers are set up, use the `useCrossmintAuth` hook to access auth state:
```tsx theme={null}
import { useCrossmintAuth } from "@crossmint/client-sdk-react-ui";
function AuthButton() {
const { login, logout, user, status } = useCrossmintAuth();
if (status === "logged-in") {
return (
Welcome, {user?.email}
);
}
return ;
}
```
## Next Steps
* [Providers](/sdk-reference/auth/react/providers) — Configure providers and their options
* [Hooks](/sdk-reference/auth/react/hooks) — Access SDK state via React hooks
* [Components](/sdk-reference/auth/react/components) — Drop-in UI components
# Hooks
Source: https://docs.crossmint.com/sdk-reference/auth/react/hooks
React hooks for React SDK reference for Crossmint authentication
## useCrossmintAuth()
Access Crossmint authentication state and actions: `login`, `logout`, the current `user`, `jwt`, and auth `status`. Must be used within a `CrossmintAuthProvider`.
### Returns
The underlying Crossmint auth client instance.
Fetch and refresh the current authenticated user.
Current JWT authentication token, if authenticated.
Trigger the login flow. Optionally pass a default email.
Configured login methods.
Initiate a login flow with the specified OAuth provider (e.g. "google", "twitter").
Log the user out and clear the session.
Authentication status: "logged-in" | "logged-out" | "in-progress" | "initializing".
The currently authenticated user object.
### Usage
```tsx theme={null}
import { useCrossmintAuth } from "@crossmint/client-sdk-react-ui";
function LoginButton() {
const { login, logout, user, status } = useCrossmintAuth();
if (status === "logged-in") {
return (
Welcome, {user?.email}
);
}
return ;
}
```
# Providers
Source: https://docs.crossmint.com/sdk-reference/auth/react/providers
React context providers for React SDK reference for Crossmint authentication
1. `CrossmintProvider` — SDK initialization (required for all Crossmint features)
2. `CrossmintAuthProvider` — Authentication state, login/logout, and the auth modal
## CrossmintProvider
### Props
Your Crossmint client-side API key.
Application identifier, sent as `x-app-identifier` header.
Minimum log level for console output (or "silent" to suppress all output). Logs below this level will not be written to the console. Set to "silent" to completely suppress console output. Defaults to "debug" (all logs shown) for backward compatibility.
Extension identifier, sent as `x-extension-id` header.
JWT token for authentication.
Override the base API URL.
### Usage
```tsx theme={null}
import { CrossmintProvider } from "@crossmint/client-sdk-react-ui";
function App() {
return (
{/* Your app content */}
);
}
```
***
## CrossmintAuthProvider
Provides Crossmint authentication to your app: email OTP, social login (OAuth), and session management. Exposes auth state via the `useCrossmintAuth` hook and renders the login modal when `login()` is called. Must be nested inside `CrossmintProvider`.
### Props
Custom UI configuration for the auth modal (colors, fonts, border radius).
Custom title for the auth modal.
Login methods to enable (e.g. `["email", "google"]`). Defaults to `["email", "google"]`.
Custom route for logging out, for server-side session management.
Callback invoked when the user successfully logs in.
Whether to prefetch OAuth provider URLs when the provider mounts. Defaults to `true`.
Custom route for refreshing the auth token, for server-side session management.
Custom storage provider for auth material. Defaults to browser cookies on web and secure device storage on React Native.
Custom terms of service text shown in the auth modal.
### Usage
```tsx theme={null}
import { CrossmintAuthProvider } from "@crossmint/client-sdk-react-ui";
function App() {
return (
console.log("Logged in!")}
authModalTitle="Sign in to My App"
>
{/* Your app content */}
);
}
```
# CrossmintAuth
Source: https://docs.crossmint.com/sdk-reference/auth/typescript/classes/CrossmintAuth
Defined in: [server/src/auth/CrossmintAuthServer.ts:38](https://github.com/Crossmint/crossmint-sdk/blob/main/packages/server/src/auth/CrossmintAuthServer.ts#L38)
Server-side Crossmint authentication client: validate sessions, verify JWTs, refresh tokens,
fetch users, and manage auth cookies. Works with both Node.js (`IncomingMessage`/`ServerResponse`)
and Fetch API (`Request`/`Response`) request/response objects.
Create an instance with [CrossmintAuthServer.from](./#from).
## Extends
* `CrossmintAuth`
## Methods
### getJwksUri()
> **getJwksUri**(): `string`
Defined in: common/auth/dist/CrossmintAuth.d.ts:14
#### Returns
`string`
#### Inherited from
`CrossmintAuth.getJwksUri`
***
### getSession()
> **getSession**(`options`, `response`?): `Promise`\<`AuthSession`>
Defined in: [server/src/auth/CrossmintAuthServer.ts:59](https://github.com/Crossmint/crossmint-sdk/blob/main/packages/server/src/auth/CrossmintAuthServer.ts#L59)
Validate the user's session and return it, refreshing it if expired.
Accepts either a request object (auth material is read from cookies) or the auth material
itself (`{ jwt, refreshToken }`). If a response object is provided, refreshed auth material
is stored in its cookies.
#### Parameters
| Parameter | Type |
| ----------- | ------------------------------------------------------------------------- |
| `options` | [`GenericRequest`](../type-aliases/GenericRequest) \| `AuthMaterialBasic` |
| `response`? | [`GenericResponse`](../type-aliases/GenericResponse) |
#### Returns
`Promise`\<`AuthSession`>
#### Throws
CrossmintAuthenticationError if no valid session can be established.
***
### getUser()
> **getUser**(`externalUserId`): `Promise`\<`any`>
Defined in: [server/src/auth/CrossmintAuthServer.ts:86](https://github.com/Crossmint/crossmint-sdk/blob/main/packages/server/src/auth/CrossmintAuthServer.ts#L86)
Fetch the Crossmint user associated with the given external user ID.
#### Parameters
| Parameter | Type |
| ---------------- | -------- |
| `externalUserId` | `string` |
#### Returns
`Promise`\<`any`>
***
### handleCustomRefresh()
> **handleCustomRefresh**(`request`, `response`?): `Promise`\<[`GenericResponse`](../type-aliases/GenericResponse)>
Defined in: [server/src/auth/CrossmintAuthServer.ts:102](https://github.com/Crossmint/crossmint-sdk/blob/main/packages/server/src/auth/CrossmintAuthServer.ts#L102)
Handle a token refresh request on a custom refresh route. Reads the refresh token from the
request body or cookies, refreshes the session, and returns a response with the new auth
material stored in cookies. Returns a 401 response if the refresh fails.
#### Parameters
| Parameter | Type |
| ----------- | ---------------------------------------------------- |
| `request` | [`GenericRequest`](../type-aliases/GenericRequest) |
| `response`? | [`GenericResponse`](../type-aliases/GenericResponse) |
#### Returns
`Promise`\<[`GenericResponse`](../type-aliases/GenericResponse)>
***
### logout()
> **logout**(`request`?, `response`?): `Promise`\<[`GenericResponse`](../type-aliases/GenericResponse)>
Defined in: [server/src/auth/CrossmintAuthServer.ts:167](https://github.com/Crossmint/crossmint-sdk/blob/main/packages/server/src/auth/CrossmintAuthServer.ts#L167)
Log the user out: invalidate the refresh token with Crossmint and clear the auth cookies on the response.
#### Parameters
| Parameter | Type |
| ----------- | ---------------------------------------------------- |
| `request`? | [`GenericRequest`](../type-aliases/GenericRequest) |
| `response`? | [`GenericResponse`](../type-aliases/GenericResponse) |
#### Returns
`Promise`\<[`GenericResponse`](../type-aliases/GenericResponse)>
***
### storeAuthMaterial()
> **storeAuthMaterial**(`response`, `authMaterial`): `void`
Defined in: [server/src/auth/CrossmintAuthServer.ts:162](https://github.com/Crossmint/crossmint-sdk/blob/main/packages/server/src/auth/CrossmintAuthServer.ts#L162)
Store auth material (JWT and refresh token) in the response's cookies.
#### Parameters
| Parameter | Type |
| -------------- | ---------------------------------------------------- |
| `response` | [`GenericResponse`](../type-aliases/GenericResponse) |
| `authMaterial` | `AuthMaterial` |
#### Returns
`void`
***
### verifyCrossmintJwt()
> **verifyCrossmintJwt**(`token`, `jwks`?): `Promise`\<`JWTPayload`>
Defined in: [server/src/auth/CrossmintAuthServer.ts:154](https://github.com/Crossmint/crossmint-sdk/blob/main/packages/server/src/auth/CrossmintAuthServer.ts#L154)
Verify a Crossmint-issued JWT and return its decoded payload. Uses Crossmint's JWKS endpoint unless a key set is provided.
#### Parameters
| Parameter | Type |
| --------- | --------------- |
| `token` | `string` |
| `jwks`? | `JSONWebKeySet` |
#### Returns
`Promise`\<`JWTPayload`>
***
### defaultApiClient()
> `static` **defaultApiClient**(`crossmint`): `CrossmintApiClient`
Defined in: common/auth/dist/CrossmintAuth.d.ts:19
#### Parameters
| Parameter | Type |
| ----------- | ----------- |
| `crossmint` | `Crossmint` |
#### Returns
`CrossmintApiClient`
#### Inherited from
`CrossmintAuth.defaultApiClient`
***
### from()
> `static` **from**(`crossmint`, `options`): `CrossmintAuthServer`
Defined in: [server/src/auth/CrossmintAuthServer.ts:47](https://github.com/Crossmint/crossmint-sdk/blob/main/packages/server/src/auth/CrossmintAuthServer.ts#L47)
Create a `CrossmintAuth` instance from a `Crossmint` object (created with `createCrossmint`).
#### Parameters
| Parameter | Type |
| ----------- | -------------------------------------------------------------------------- |
| `crossmint` | `Crossmint` |
| `options` | [`CrossmintAuthServerOptions`](../type-aliases/CrossmintAuthServerOptions) |
#### Returns
`CrossmintAuthServer`
#### Overrides
`CrossmintAuth.from`
# createCrossmint
Source: https://docs.crossmint.com/sdk-reference/auth/typescript/functions/createCrossmint
> **createCrossmint**(`config`, `apiKeyExpectations`?): `Crossmint`
Defined in: common/base/dist/types/Crossmint.d.ts:19
## Parameters
| Parameter | Type |
| --------------------- | ---------------------------------- |
| `config` | `CrossmintConfig` |
| `apiKeyExpectations`? | `ValidateAPIKeyPrefixExpectations` |
## Returns
`Crossmint`
# verifyCrossmintJwt
Source: https://docs.crossmint.com/sdk-reference/auth/typescript/functions/verifyCrossmintJwt
> **verifyCrossmintJwt**(`token`, `jwks`): `Promise`\<`JWTPayload`>
Defined in: [server/src/auth/utils/jwt.ts:9](https://github.com/Crossmint/crossmint-sdk/blob/main/packages/server/src/auth/utils/jwt.ts#L9)
Verify a Crossmint-issued JWT against the given JWKS (endpoint URI or key set) and return its decoded payload.
## Parameters
| Parameter | Type |
| --------- | ---------------------------------------- |
| `token` | `string` |
| `jwks` | [`JWKSInput`](../type-aliases/JWKSInput) |
## Returns
`Promise`\<`JWTPayload`>
# Getting Started
Source: https://docs.crossmint.com/sdk-reference/auth/typescript/overview
### Latest Node.js SDK version -
The Crossmint server SDK (`@crossmint/server-sdk`) provides a set of tools for authenticating users in a Crossmint-powered application using server-side rendering (SSR). It simplifies the process of handling authentication tokens and managing user sessions, making it easier to integrate authentication into your Next.js applications.
## Installation
To install the SDK, you can use npm or yarn:
```bash theme={null}
npm install @crossmint/server-sdk
```
## Usage
To use the SDK in your application, follow these steps:
1. Import the SDK into your project:
```ts theme={null}
import { createCrossmint, CrossmintAuth } from "@crossmint/server-sdk";
const crossmint = createCrossmint({
apiKey: process.env.SERVER_CROSSMINT_API_KEY || "",
});
const crossmintAuth = CrossmintAuth.from(crossmint);
```
2. Use the SDK to authenticate users:
With most frameworks, pass the request object:
```ts theme={null}
const { jwt, userId } = await crossmintAuth.getSession(request);
```
With Next.js, fetch the cookies and pass them to the `getSession` method:
```ts theme={null}
import { cookies } from "next/headers";
const cookieStore = cookies();
const jwtCookie = cookieStore.get("crossmint-jwt")?.value;
const refreshCookie = cookieStore.get("crossmint-refresh-token")?.value;
const { jwt, userId } = await crossmintAuth.getSession({
jwt: jwtCookie,
refreshToken: refreshCookie,
});
```
3. Store the authentication material in cookies
If you are using a framework with access to the response object, you can store the authentication material in cookies by passing the response object to the `getSession` method:
```ts theme={null}
const { jwt, userId } = await crossmintAuth.getSession(request, response);
```
4. Logout
```ts theme={null}
await crossmintAuth.logout(request);
```
## Security
The SDK allows you to set the `httpOnly`, `secure`, `domain` and `sameSite` options for the cookies. This way, you can control how the cookies are stored and transmitted. Putting this together with a custom refresh route, you can store the authentication material in HttpOnly cookies that are tied to the domain of the provided route.
Configure CrossmintAuth to do so when creating the custom refresh route:
```ts theme={null}
const crossmintAuth = CrossmintAuth.from(crossmint, {
cookieOptions: {
httpOnly: true,
sameSite: "Strict",
secure: true,
domain: ".example.com",
},
});
```
`httpOnly` only applies to the refresh token. The session token will not be HttpOnly as it is used in the client for API calls.
## Set up a custom refresh route
To set up a custom refresh route, you can use the `handleCustomRefresh` method. This method will refresh the token and return the new authentication material. This way, the authentication material can be stored in cookies that are tied to the domain of the provided route.
In environments that use the Fetch API for `Request` and `Response` objects, `handleCustomRefresh` will return the response object:
```ts theme={null}
return await crossmintAuth.handleCustomRefresh(request);
```
In environments that use Node.js API, you also need to provide the response object and end the response:
```ts theme={null}
await crossmintAuth.handleCustomRefresh(req, res);
res.end();
```
### Using a custom refresh route
You can also provide a custom refresh route:
```typescript theme={null}
const crossmintAuth = CrossmintAuth.from(crossmint, {
refreshRoute: "/api/refresh",
});
```
This way, the SDK will use the provided route to refresh the token instead of the default one and the authentication material can be stored in HttpOnly cookies that are tied to the domain of the provided route.
## Set up a custom logout route
When using `HttpOnly` cookies, logout can't happen client-side as it doesn't have access to the cookies. You can set up a custom logout route to handle the logout process.
In environments that use the Fetch API, `logout` will return the response object:
```ts theme={null}
return await crossmintAuth.logout(request);
```
In environments that use Node.js API, you also need to provide the response object and end the response:
```ts theme={null}
await crossmintAuth.logout(req, res);
res.end();
```
# Reference
Source: https://docs.crossmint.com/sdk-reference/auth/typescript/reference
## Classes
* [CrossmintAuth](./classes/CrossmintAuth)
## Type Aliases
* [CrossmintAuthServerOptions](./type-aliases/CrossmintAuthServerOptions)
* [GenericRequest](./type-aliases/GenericRequest)
* [GenericResponse](./type-aliases/GenericResponse)
* [JWKSInput](./type-aliases/JWKSInput)
## Variables
* [CROSSMINT\_API\_VERSION](./variables/CROSSMINT_API_VERSION)
* [SDK\_NAME](./variables/SDK_NAME)
* [SDK\_VERSION](./variables/SDK_VERSION)
## Functions
* [createCrossmint](./functions/createCrossmint)
* [verifyCrossmintJwt](./functions/verifyCrossmintJwt)
# CrossmintAuthServerOptions
Source: https://docs.crossmint.com/sdk-reference/auth/typescript/type-aliases/CrossmintAuthServerOptions
> **CrossmintAuthServerOptions** = `CrossmintAuthOptions` & `object`
Defined in: [server/src/auth/CrossmintAuthServer.ts:26](https://github.com/Crossmint/crossmint-sdk/blob/main/packages/server/src/auth/CrossmintAuthServer.ts#L26)
## Type Declaration
### cookieOptions?
> `optional` **cookieOptions**: `CookieOptions`
Options applied to the auth cookies set by the SDK (e.g. `domain`).
# GenericRequest
Source: https://docs.crossmint.com/sdk-reference/auth/typescript/type-aliases/GenericRequest
> **GenericRequest** = `IncomingMessage` | `Request`
Defined in: [server/src/auth/types/request.ts:4](https://github.com/Crossmint/crossmint-sdk/blob/main/packages/server/src/auth/types/request.ts#L4)
# GenericResponse
Source: https://docs.crossmint.com/sdk-reference/auth/typescript/type-aliases/GenericResponse
> **GenericResponse** = `ServerResponse` | `Response`
Defined in: [server/src/auth/types/request.ts:5](https://github.com/Crossmint/crossmint-sdk/blob/main/packages/server/src/auth/types/request.ts#L5)
# JWKSInput
Source: https://docs.crossmint.com/sdk-reference/auth/typescript/type-aliases/JWKSInput
> **JWKSInput** = `string` | `JSONWebKeySet`
Defined in: [server/src/auth/utils/jwt.ts:6](https://github.com/Crossmint/crossmint-sdk/blob/main/packages/server/src/auth/utils/jwt.ts#L6)
A JWKS endpoint URI or an inline JSON Web Key Set.
# CROSSMINT_API_VERSION
Source: https://docs.crossmint.com/sdk-reference/auth/typescript/variables/CROSSMINT_API_VERSION
> `const` **CROSSMINT\_API\_VERSION**: `"2024-09-26"` = `"2024-09-26"`
Defined in: [server/src/auth/utils/constants.ts:5](https://github.com/Crossmint/crossmint-sdk/blob/main/packages/server/src/auth/utils/constants.ts#L5)
# SDK_NAME
Source: https://docs.crossmint.com/sdk-reference/auth/typescript/variables/SDK_NAME
> `const` **SDK\_NAME**: `string` = `name`
Defined in: [server/src/auth/utils/constants.ts:4](https://github.com/Crossmint/crossmint-sdk/blob/main/packages/server/src/auth/utils/constants.ts#L4)
# SDK_VERSION
Source: https://docs.crossmint.com/sdk-reference/auth/typescript/variables/SDK_VERSION
> `const` **SDK\_VERSION**: `string` = `version`
Defined in: [server/src/auth/utils/constants.ts:3](https://github.com/Crossmint/crossmint-sdk/blob/main/packages/server/src/auth/utils/constants.ts#L3)
# CrossmintCheckoutAppearance
Source: https://docs.crossmint.com/sdk-reference/checkout/flutter/classes/crossmint-checkout-appearance
Flutter Final Class
**Final Class**
UI customization for the hosted checkout page.
```dart theme={null}
final class CrossmintCheckoutAppearance
```
Supports three levels of customization:
* `variables` — CSS custom properties (e.g. `colorPrimary`, `borderRadius`)
* `rules` — CSS rules targeting specific elements (e.g. `.Input`, `.Tab`)
* `fonts` — custom web fonts via `cssSrc`
## Constructors
### CrossmintCheckoutAppearance
```dart theme={null}
const CrossmintCheckoutAppearance({this.variables, this.rules, this.fonts})
```
## Properties
### variables
```dart theme={null}
final Map? variables
```
CSS custom properties for theming (e.g. `{'colorPrimary': '#0F172A'}`).
### rules
```dart theme={null}
final Map? rules
```
CSS rules for specific checkout elements (e.g. `{'.Input': {'borderColor': '#CBD5E1'}}`).
### fonts
```dart theme={null}
final List