# Get Usage
Source: https://docs.crossmint.com/api-reference/admin/get-usage
get /v1-alpha1/projects/{projectId}/usage
Get usage data for a project.
**API scope required** `projects:usage.read`
# Get Action Status
Source: https://docs.crossmint.com/api-reference/common/get-action-status
get /2022-06-09/actions/{actionId}
Use this API to poll for the status of asynchonous actions such as NFT mints, transfers, etc.
**API scope required**: `nfts.create`
The shape of the data property in the `200` response for this API depends on the type of action initially performed.
For example, minting/burning an NFT, creating/updating a collection or template, or NFT transfers.
The `action` property will indicate the type of action originally performed and will be one of:
* `nfts.create`
* `nfts.delete`
* `nfts.update`
* `collections.create`
* `collections.update`
* `wallets:nfts.transfer`
# Check Token Support
Source: https://docs.crossmint.com/api-reference/headless/check-token-support
GET /v1-alpha2/tokens/{tokenLocator}
Get a token by its locator
# Create Order
Source: https://docs.crossmint.com/api-reference/headless/create-order
post /2022-06-09/orders
Creates a new order that can be used to complete a headless checkout.
**API scope required**: `orders.create`
# Edit Order
Source: https://docs.crossmint.com/api-reference/headless/edit-order
patch /2022-06-09/orders/{orderId}
Edit an existing order. You can update the recipient, the payment method, and/or the locale.
**API scope required**: `orders.update`
# Get Order
Source: https://docs.crossmint.com/api-reference/headless/get-order
get /2022-06-09/orders/{orderId}
Get specific order by ID.
**Authentication**: Use either a server-side API key with `orders.read` scope, or the `clientSecret` from order creation as an authorization header.
# Introduction
Source: https://docs.crossmint.com/api-reference/introduction
Explore the APIs and test them live from the browser
This section provides a detailed guide for each of the APIs, with sample code and responses.
## Testing the APIs from the browser
The reference pages allow you to call the APIs directly from the browser. To get started:
1. Create a developer account on the [Staging](https://staging.crossmint.com/console) or [Production](https://www.crossmint.com/console) consoles. Read more about the environments [here](/introduction/platform/staging-vs-production).
2. Create an [API key](/introduction/platform/api-keys) from the `API keys` tab on the console, with the permissions required for the APIs you want to use.
3. From the page of your API of choice, insert the right API key in the authorization slot, introduce the different parameters, and call "Send".
4. (Optional) To call APIs in the production environment, find the API endpoint selector (`⌄`) next to the endpoint URL, and change it to `www.crossmint.com/api`.
The following guide will show you how to create a wallet and mint an NFT into it within 5 minutes. For this, make sure your API key has the scopes `wallets.create`, and `nfts.create`.
Follow the next steps to create a wallet within 5 minutes:
You may add any additional properties. Each API reference page includes the full list of properties and admissible values.
Scroll back to the top of the page and click the blue `Send` button to trigger
the API call.
Here is an example response object:
**Success!** The next guide will show you how to mint an NFT into this wallet and view the content.
In the previous guide, you created a wallet. Now you will deliver an NFT into it and read the wallet's content.
This is a default collection associated to your account. You can create new ones using the [Create Collections](/minting/nfts/integrate/create-collections) API.
The selector to choose between the different metadata option can be hard to spot. Look closely at the screenshots below to see where it is.
You can provide your own or use this example URL:
```bash metadata URL theme={null}
https://bafkreidbf4jpxpecwezjbagwmua5qv62ifhhtzsspoml7zls6iq6ms4byi.ipfs.nftstorage.link/
```
```json example JSON 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": "contract",
"value": "ERC-721"
},
{
"trait_type": "background",
"value": "black"
}
]
}
```
This URL must point to a valid JSON file that adheres to [EVM metadata standards](/minting/nfts/integrate/define-metadata).
There are two options for the `recipient` parameter. The first enables minting to an email address. The second enables minting directly to a wallet address.
```javascript email recipient theme={null}
// format
email::
// example
email:testy@crossmint.xyz:polygon
```
```javascript wallet recipient theme={null}
// format
:
// example
polygon:0x0359Cd99FD20e853a85489DFC93EaDFeF7461590
```
Enter your own email address on this step and you can login to see your NFT in the Crossmint wallet later, which can be accessed from [crossmint.com](https://crossmint.com) or [staging.crossmint.com](https://staging.crossmint.com) if you are in staging.
This will prevent unnecessary uploading of files that are already pinned to IPFS.
Crossmint has a 10MB re-upload size limit. For larger files, upload your media to IPFS, create a metadata.json as shown above, submit the metadata file's URL, and ensure `reuploadLinkedFiles` is set to `false`.
The default option is `true`.
Scroll back to the top of the page and click the blue `Send` button to trigger
the API call.
Here is an example response object:
If you followed this guide closely you can view the NFT in the staging wallet at [https://staging.crossmint.com/user/collection](https://staging.crossmint.com/user/collection).
If you minted directly to a wallet address you can view the NFT in that application. Ensure you are accessing it from a testnet if you used staging.
# Create IP Asset
Source: https://docs.crossmint.com/api-reference/ip/create-ip-asset
post /v1/ip/collections/{collectionId}/ipassets
Create a new IP Asset
**API scope required**: `nfts.create`
This API is still under development. Contact support for early access.{" "}
# Create IP Asset (Idempotent)
Source: https://docs.crossmint.com/api-reference/ip/create-ip-asset-idempotent
put /v1/ip/collections/{collectionId}/ipassets/{customerFacingId}
Create a new IP Asset with a pre-computed id, or get an existing one if the id already exists
**API scope required**: `nfts.create`
This API is still under development. Contact support for early access.{" "}
# Create IP Collection
Source: https://docs.crossmint.com/api-reference/ip/create-ip-collection
post /v1/ip/collections
Create a new collection with a pre-computed id, or get an existing one if the id already exists
**API scope required**: `collections.create`
This API is still under development. Contact support for early access.
# Create IP Collection (Idempotent)
Source: https://docs.crossmint.com/api-reference/ip/create-ip-collection-idempotent
put /v1/ip/collections/{collectionId}
Create a new collection with a pre-computed id, or get an existing one if the id already exists
**API scope required**: `collections.create`
This API is still under development. Contact support for early access.
# Get All IP Collections
Source: https://docs.crossmint.com/api-reference/ip/get-all-ip-collections
get /v1/ip/collections
Get all collections associated with the Developer Project
**API scope required**: `collections.read`
This API is still under development. Contact support for early access.
# Get IP Action
Source: https://docs.crossmint.com/api-reference/ip/get-ip-action
get /v1/ip/actions/{actionId}
Get an action by its id
**API scope required**: `nfts.create`
This API is still under development. Contact support for early access.
# Get IP Asset
Source: https://docs.crossmint.com/api-reference/ip/get-ip-asset
get /v1/ip/collections/{collectionId}/ipassets/{customerFacingId}
Get a single IP Asset
**API scope required**: `nfts.read`
This API is still under development. Contact support for early access.{" "}
# Get IP Assets in Collection
Source: https://docs.crossmint.com/api-reference/ip/get-ip-assets
get /v1/ip/collections/{collectionId}/ipassets
Get all IP Assets in a collection
**API scope required**: `nfts.read`
This API is still under development. Contact support for early access.{" "}
# Get IP Collection
Source: https://docs.crossmint.com/api-reference/ip/get-ip-collection
get /v1/ip/collections/{collectionId}
Get a collection by its id deployed on the Story chain
**API scope required**: `collections.read`
This API is still under development. Contact support for early access.
# Get IP Asset Graph
Source: https://docs.crossmint.com/api-reference/ip/get-ip-graph
get /v1/ip/graph/{ipAssetId}
Get the graph of an IP Asset, by default it will fetch the first level of parents and children (depth = 1). You can customize the depth using the query parameter 'depth' to a maximum of 3. Maximum 100 parents or children will be returned for each level.
The ipAssetId parameter should be the Story Protocol asset ID (not the Crossmint ID). Must start with '0x' followed by hexadecimal characters.
**API scope required**: `nfts.read`
This API is still under development. Contact support for early access.{" "}
# Get IP Asset License
Source: https://docs.crossmint.com/api-reference/ip/get-ip-license
get /v1/ip/licenses/{ipassetId}
Get the licenses of an IP Asset
The ipassetId parameter should be the Story Protocol asset ID (not the Crossmint ID). Must start with '0x' followed by hexadecimal characters.
**API scope required**: `nfts.read`
This API is still under development. Contact support for early access.{" "}
# Update IP Asset
Source: https://docs.crossmint.com/api-reference/ip/update-ip-asset
patch /v1/ip/collections/{collectionId}/ipassets/{customerFacingId}
Update an existing IP Asset
**API scope required**: `nfts.update`
This API is still under development. Contact support for early access.{" "}
# Create Collection
Source: https://docs.crossmint.com/api-reference/minting/collection/create-collection
post /2022-06-09/collections/
Create a collection that you can mint NFTs/SFTs from
**API scope required**: `collections.create`
# Create Collection (Idempotent)
Source: https://docs.crossmint.com/api-reference/minting/collection/create-collection-idempotent
put /2022-06-09/collections/{collectionId}
Create a collection that you can mint NFTs/SFTs from. This API is idempotent,
if you call it multiple times with the same ID, only one will be created.
**API scope required**: `collections.create`
# Get All Collections
Source: https://docs.crossmint.com/api-reference/minting/collection/get-all-collections
get /2022-06-09/collections/
List all collections created under the current Crossmint project
**API scope required**: `collections.read`
For Solana collections the `onChain.contractAddress` property will be named `onChain.mintAddress`
```json 200 theme={null}
{
"results": [
{
"id": "bb691876-edb3-404c-af3e-c019b8e2ed2c",
"metadata": {
"name": "Test Collection",
"description": "Test",
"imageUrl": "ipfs://QmVocoiYXZLAtheEHV3VF8w4pa68bkPutT8cQZdMrrpzxh",
"symbol": "XMINT"
},
"fungibility": "non-fungible",
"onChain": {
"chain": "polygon",
"type": "erc-721",
"contractAddress": "0x9564bD85f3D5677D86244dDb06F06bbD22D9d0DB"
},
"supplyLimit": 95,
"payments": {
"price": "0.001",
"recipientAddress": "0x6C3b3225759Cbda68F96378A9F0277B4374f9F06"
}
}
]
}
```
# Get Base URI
Source: https://docs.crossmint.com/api-reference/minting/collection/get-base-uri
get /v1-alpha1/minting/collections/{collectionId}/base-uri
Get the Base URI of a collection as it appears onchain.
**API scope required**: `collections.read`
# Get Collection Info
Source: https://docs.crossmint.com/api-reference/minting/collection/get-collection
get /2022-06-09/collections/{collectionId}
Get information about a specific collection.
**API scope required**: `collections.read`
For Solana collections the `onChain.contractAddress` property will be named `onChain.mintAddress`
# Get Royalties Configuration
Source: https://docs.crossmint.com/api-reference/minting/collection/get-royalties-configuration
get /v1-alpha1/minting/collections/{collectionId}/royalties
Fetch the royalty configuration for a collection, from its current state
in the blockchain.
This API is only supported on EVM chains.
If you call GET too soon after PUT/DELETE,
you may not yet see your latest changes, as they can take a few seconds to
record on the blockchain.
**API scope required**: `collections.read`
# Get Transferability
Source: https://docs.crossmint.com/api-reference/minting/collection/get-transferability
get /v1-alpha1/minting/collections/{collectionId}/transferable
Get the transferable status of a collection.
This API is supported on EVM and Aptos chains.
You must contact sales to gain access to this API.
**API scope required**: `collections.read`
# Remove Royalties
Source: https://docs.crossmint.com/api-reference/minting/collection/remove-royalties
delete /v1-alpha1/minting/collections/{collectionId}/royalties
Remove all royalties from a given collection. No new NFT sales will yield royalties to the creator.
This API is only supported on EVM Chains.
**API scope required**: `collections.update`
# Set Base URI
Source: https://docs.crossmint.com/api-reference/minting/collection/set-base-uri
put /v1-alpha1/minting/collections/{collectionId}/base-uri
Update the Base URI of a collection. Setting the baseURI enables
excluding the metadata param when minting. Tokens minted without the metadata
param will have a tokenURI of:
`{BASE_URI}/{TOKEN_ID}`
This API is currently only supported on EVM Chains.
**API scope required**: `collections.update`
# Set Royalties
Source: https://docs.crossmint.com/api-reference/minting/collection/set-royalties
put /v1-alpha1/minting/collections/{collectionId}/royalties
Configure royalties for all NFTs in a collection.
This API is only supported for EVM chains and implements the EIP-2981 standard.
**API scope required**: `collections.update`
# Set Transferability
Source: https://docs.crossmint.com/api-reference/minting/collection/set-transferability
put /v1-alpha1/minting/collections/{collectionId}/transferable
Update the transferable status of a collection.
This API is supported on EVM and Aptos chains.
You must contact sales to gain access to this API.
**API scope required**: `collections.update`
# Update Collection
Source: https://docs.crossmint.com/api-reference/minting/collection/update-collection
patch /2022-06-09/collections/{collectionId}
Update the sales details of a collection
**API scope required**: `collections.update`
# Burn NFT
Source: https://docs.crossmint.com/api-reference/minting/nfts/burn-nft
delete /2022-06-09/collections/{collectionId}/nfts/{id}
Burn a minted NFT.
**API scope required**: `nfts.delete`
# Edit NFT
Source: https://docs.crossmint.com/api-reference/minting/nfts/edit-nft
patch /2022-06-09/collections/{collectionId}/nfts/{id}
Edit a minted NFT's metadata on IPFS.
If you are using a custom baseURI, invoking this will overwrite the specific tokenURI for the edited token.
**API scope required**: `nfts.update`
# Get All NFTs
Source: https://docs.crossmint.com/api-reference/minting/nfts/get-nfts
get /2022-06-09/collections/{collectionId}/nfts
Get a list of all the NFTs in a given collection.
**API scope required**: `nfts.read`
# Mint NFT
Source: https://docs.crossmint.com/api-reference/minting/nfts/mint-nft
post /2022-06-09/collections/{collectionId}/nfts
Mint your NFTs and deliver them to a web3 wallet or an email address
**API scope required**: `nfts.create`
For uncompressed Solana NFTs, [contact sales](https://www.crossmint.com/contact/sales) for access.
# Mint NFT with ID
Source: https://docs.crossmint.com/api-reference/minting/nfts/mint-nft-idempotent
put /2022-06-09/collections/{collectionId}/nfts/{id}
This pathway allows you to mint NFTs and guarantee idempotency
to ensure you never double mint for the same NFT.
**API scope required**: `nfts.create`
Subsequent requests to this endpoint with the same `id` in the path will ***not*** mint additional NFTs.
Furthermore, the success responses (with status code 200) will be different once the initial request has completed and includes the metadata for the minted NFT.
```json EVM theme={null}
{
"id": "",
"metadata": {
"name": "",
"image": "",
"description": ""
},
"onChain": {
"status": "",
"tokenId": "",
"owner": "",
"txId": "",
"contractAddress": "",
"chain": ""
},
"actionId": ""
}
```
```json Solana theme={null}
{
"id": "",
"metadata": {
"name": "",
"symbol": "",
"description": "",
"seller_fee_basis_points": 0,
"image": "",
"attributes": [],
"properties": {}
},
"onChain": {
"status": "success",
"mintHash": "",
"txId": "",
"owner": "",
"chain": "solana"
},
"actionId": ""
}
```
# Mint SFT
Source: https://docs.crossmint.com/api-reference/minting/nfts/mint-sft
post /2022-06-09/collections/{collectionId}/sfts
Mint your SFTs and deliver them to a web3 wallet or an email address
**API scope required**: `nfts.create`
# Mint Status
Source: https://docs.crossmint.com/api-reference/minting/nfts/mint-status
get /2022-06-09/collections/{collectionId}/nfts/{id}
Get the status and associated information for a mint operation.
**API scope required**: `nfts.read`
```json 200 EVM theme={null}
{
"id": "",
"metadata": {
"name": "",
"image": "",
"description": ""
},
"onChain": {
"status": "success",
"tokenId": "",
"owner": "",
"txId": "",
"contractAddress": "",
"chain": "polygon"
},
"action": "https://staging.crossmint.com/api/2022-06-09/actions/"
}
```
```json 200 Solana theme={null}
{
"id": "",
"metadata": {
"name": "",
"symbol": "",
"seller_fee_basis_points": 0,
"properties": {},
"description": "",
"image": "",
"attributes": []
},
"onChain": {
"status": "success",
"mintHash": "",
"txId": "",
"owner": "",
"chain": "solana"
},
"action": "https://staging.crossmint.com/api/2022-06-09/actions/"
}
```
# Create Template
Source: https://docs.crossmint.com/api-reference/minting/template/create-template
post /2022-06-09/collections/{collectionId}/templates
Create a token template, that NFTs or SFTs may be minted from
**API scope required**: `nfts.create`
# Create Template with ID
Source: https://docs.crossmint.com/api-reference/minting/template/create-template-idempotent
put /2022-06-09/collections/{collectionId}/templates/{templateId}
Create a token template with preconfigured metadata
**API scope required**: `nfts.create`
# Delete Template
Source: https://docs.crossmint.com/api-reference/minting/template/delete-template
delete /2022-06-09/collections/{collectionId}/templates/{templateId}
Delete a Token template.
**API scope required**: `nfts.delete`
# Edit Template
Source: https://docs.crossmint.com/api-reference/minting/template/edit-template
patch /2022-06-09/collections/{collectionId}/templates/{templateId}
Edit a Token template.
**API scope required**: `nfts.update`
# Get All Templates
Source: https://docs.crossmint.com/api-reference/minting/template/get-all-templates
get /2022-06-09/collections/{collectionId}/templates
Get all of the templates for a collection
**API scope required**: `nfts.read`
# Get Template
Source: https://docs.crossmint.com/api-reference/minting/template/get-template
get /2022-06-09/collections/{collectionId}/templates/{templateId}
Fetch the contents of a token template.
**API scope required**: `nfts.read`
# Issue Credential
Source: https://docs.crossmint.com/api-reference/verifiable-credentials/credentials/issue-credential
post /v1-alpha1/credentials/templates/{templateId}/vcs
Issue a credential and deliver it to a wallet or email address.
**API scope required** `credentials.create`
# Get Credential by Credential ID
Source: https://docs.crossmint.com/api-reference/verifiable-credentials/credentials/retrieve-credential-by-id
get /v1-alpha1/credentials/{id}
Get a verifiable credential by the ID associated with it.
This ID will have the format: `urn:uuid:`. For example: `urn:uuid:64f9877d-a19a-4205-8d61-f8c2abed5766`
**API scope required** `credentials.read`. This enpoint will work also with a client side API key.
# Get Credential by NFT ID
Source: https://docs.crossmint.com/api-reference/verifiable-credentials/credentials/retrieve-credential-by-nft
get /v1-alpha1/credentials/templates/{collectionId}/nfts/{id}/credentials
Get a verifiable credential by the ID associated with the minted NFT.
This ID will have the format: ``. For example: `d7eb777b-e9b4-4f34-ab5f-ce199111166a`
**API scope required** `credentials.read`. This endpoint will not work with a client side API key.
# Get Credential by NFT Locator
Source: https://docs.crossmint.com/api-reference/verifiable-credentials/credentials/retrieve-credential-by-nft-locator
get /v1-alpha1/nfts/{nftLocator}/credentials
Get a verifiable credential by the NFT locator.
This locator will have the format: `::`.
For example: `polygon:0x1234abcde...:1`
**API scope required** `credentials.read`. This enpoint will work also with a client side API key.
# null
Source: https://docs.crossmint.com/api-reference/verifiable-credentials/credentials/retrieve-credential-nfts
get /api/v1-alpha1/wallets/{walletLocator}/credential_nfts
# Revoke Credential
Source: https://docs.crossmint.com/api-reference/verifiable-credentials/credentials/revoke-credential
DELETE /v1-alpha1/credentials/{id}
Revoke a verifiable credential by the credential ID.
This involves burning the associated nft.
This ID will have the format: `urn:uuid:`. For example: `urn:uuid:64f9877d-a19a-4205-8d61-f8c2abed5766`
**API scope required** `credentials.create`.
# Verify Credential
Source: https://docs.crossmint.com/api-reference/verifiable-credentials/credentials/verify-credential
post /v1-alpha1/credentials/verification/verify
Verify that a verifiable credential is valid.
**API scope required** `credentials.read`
It is impractical to use the API Playground to verify a credential. Instead you can copy the sample code for your
preferred language in the righthand sidebar and fill in the JSON of the credential you intend to verify.
Here is an example of how to verify a credential using javascript:
```javascript verifyVC.js theme={null}
const options = {
method: "POST",
headers: {
"X-API-KEY": "YOUR_API_KEY",
"Content-Type": "application/json",
},
body: '{"credential": {"id":"urn:uuid:e31316b6-3be1-49af-a95f-c7f4c3f52aa1","credentialSubject":{"course":"Blockchain 101","passed":true,"id":"did:polygon:0x6C3b3225759Cbda68F96378A9F0277B4374f9F06"},"expirationDate":"2034-02-03","nft":{"tokenId":"1","chain":"polygon","contractAddress":"0xdC444A3F4768185497Dae6250E2F348b99bE89F3"},"issuer":{"id":"did:polygon:0xa22CaDEdE67c11dc1444E507fDdd9b831a67aBd1"},"type":["VerifiableCredential","65c15243e9bee8deac219d57"],"issuanceDate":"2024-02-05T23:21:32.641Z","@context":["https://www.w3.org/2018/credentials/v1","https://github.com/haardikk21/ethereum-eip712-signature-2021-spec/blob/main/index.html","DUMMY_CROSSMINT/65c15243e9bee8deac219d57"],"proof":{"verificationMethod":"did:polygon:0xa22CaDEdE67c11dc1444E507fDdd9b831a67aBd1#ethereumAddress","ethereumAddress":null,"created":"2024-02-05T23:22:24.058Z","proofPurpose":"assertionMethod","type":"EthereumEip712Signature2021","proofValue":"0x748a55d9770cbc6ef16689f0d9547355e288bcce03a8949a32d2aac59244cb9e08cbf54c960bc00d133fc51e6651a4ac1366aeda320dd121777118a4e74980631b","eip712":{"domain":{"name":"Krebit","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":"issuanceDate","type":"string"},{"name":"expirationDate","type":"string"},{"name":"nft","type":"Nft"}],"CredentialSubject":[{"name":"id","type":"string"},{"name":"course","type":"string"},{"name":"passed","type":"bool"}],"Issuer":[{"name":"id","type":"string"}],"Nft":[{"name":"tokenId","type":"string"},{"name":"contractAddress","type":"string"},{"name":"chain","type":"string"}]},"primaryType":"VerifiableCredential"}}}}',
};
// note the gigantic VC JSON string in the body property
fetch("https://staging.crossmint.com/api/v1-alpha1/credentials/verification/verify", options)
.then((response) => response.json())
.then((response) => console.log(response))
.catch((err) => console.error(err));
```
```json 200 valid theme={null}
{
"isValid": true,
"error": null
}
```
```json 200 revoked theme={null}
{
"isValid": false,
"error": "Credential Revoked"
}
```
```json 200 expired theme={null}
{
"isValid": false,
"error": "Credential expired at "
}
```
```json 200 invalid_proof theme={null}
{
"isValid": false,
"error": "Invalid proof"
}
```
# Create Credential template
Source: https://docs.crossmint.com/api-reference/verifiable-credentials/templates/create-template
post /v1-alpha1/credentials/templates/
Create a template, similar to an NFT collection, for issuing verifiable credentials.
**API scope required** `credentials:template.create`
# Create Credential Type with Name
Source: https://docs.crossmint.com/api-reference/verifiable-credentials/types/create-named-type
put /v1-alpha1/credentials/types/{typeName}
Create or import a type with a given name. This is how you define a custom credential schema.
**API scope required** `credentials.create`
# Create Credential Type
Source: https://docs.crossmint.com/api-reference/verifiable-credentials/types/create-type
post /v1-alpha1/credentials/types
Create or import a credential type with a random UUID. This is how you define a custom credential schema.
**API scope required** `credentials.create`
# Get a Credential Type
Source: https://docs.crossmint.com/api-reference/verifiable-credentials/types/get-type
get /v1-alpha1/credentials/types/{typeName}
Get the schema of a given type by name (or id)
**API scope required** `credentials.read`
# Approve Signature
Source: https://docs.crossmint.com/api-reference/wallets/approve-signature
post /2025-06-09/wallets/{walletLocator}/signatures/{signatureId}/approvals
Submit approval for a signature to sign a message or typed data.
**API scope required**: `wallets:signatures.create`
# Approve Transaction
Source: https://docs.crossmint.com/api-reference/wallets/approve-transaction
post /2025-06-09/wallets/{walletLocator}/transactions/{transactionId}/approvals
Submit approval signature for a pending transaction. Required for transactions using external signers.
**API scope required**: `wallets:transactions.sign`
# Create Signature
Source: https://docs.crossmint.com/api-reference/wallets/create-signature
post /2025-06-09/wallets/{walletLocator}/signatures
Creates a new signature for signing messages or typed data.
**API scope required**: `wallets:signatures.create`
# Create Transaction
Source: https://docs.crossmint.com/api-reference/wallets/create-transaction
post /2025-06-09/wallets/{walletLocator}/transactions
Creates a new transaction for the specified wallet. Transaction will be automatically broadcast once it has all necessary approvals.
**API scope required**: `wallets:transactions.create`
# Create Wallet
Source: https://docs.crossmint.com/api-reference/wallets/create-wallet
post /2025-06-09/wallets
Creates a new wallet of specified type. If called with an idempotency key or for a user who already has a wallet, returns existing wallet. When owner is provided, subsequent calls with the same owner will return the existing wallet. Supports both custodial and non-custodial wallet types.
**API scope required**: `wallets.create`
# Fund Wallet
Source: https://docs.crossmint.com/api-reference/wallets/fund-wallet
post /v1-alpha2/wallets/{walletLocator}/balances
Send funds to a wallet.
**API scope required**: `wallets.fund`
This endpoint is only available in **staging** and only supports **USDXM**.
# Get NFTs from Wallet
Source: https://docs.crossmint.com/api-reference/wallets/get-nfts-from-wallet
get /2022-06-09/wallets/{identifier}/nfts
Fetch the NFTs in a provided wallet
**API scope required**: `wallets:nfts.read`
This API enables fetching the NFTs for a provided wallet address and chain.
The response will be slightly different between EVM, Solana, and other wallets. See the example responses to the right.
```json 200 EVM theme={null}
[
{
"chain": "",
"contractAddress": "",
"tokenId": "",
"metadata": {
"attributes": [],
"collection": {},
"description": "",
"image": "",
"animation_url": "",
"name": ""
},
"locator": "",
"tokenStandard": ""
}
]
```
```json 200 EVM (with subscription) theme={null}
[
{
"chain": "",
"contractAddress": "",
"tokenId": "",
"metadata": {
"attributes": [],
"collection": {},
"description": "",
"image": "",
"animation_url": "",
"name": ""
},
"locator": "",
"tokenStandard": "",
"subscription": {
"expiresAt": ""
}
}
]
```
```json 200 Solana theme={null}
[
{
"chain": "",
"mintHash": "",
"metadata": {
"name": "",
"description": "",
"image": "",
"attributes": []
},
"locator": ""
}
]
```
{/* prettier-ignore */}
{/*
```json 200 Cardano
[
{
"chain": "",
"assetId": "",
"metadata": {
"assetId": "",
"name": "",
"description": "",
"image": "",
"attributes": []
},
"locator": ""
}
]
```
prettier-ignore
\*/}
```json 400 theme={null}
{
"error": ""
}
```
# Get Signature
Source: https://docs.crossmint.com/api-reference/wallets/get-signature
get /2025-06-09/wallets/{walletLocator}/signatures/{signatureId}
Retrieves details about a specific signature by its ID.
**API scope required**: `wallets:signatures.read`
# Get All Signatures
Source: https://docs.crossmint.com/api-reference/wallets/get-signatures
get /2025-06-09/wallets/{walletLocator}/signatures
Retrieves all signatures associated with the specified wallet.
**API scope required**: `wallets:signatures.read`
# Get Delegated Signer
Source: https://docs.crossmint.com/api-reference/wallets/get-signer
get /2025-06-09/wallets/{walletLocator}/signers/{signer}
Retrieve details about a specific delegated signer by its locator.
**API scope required**: `wallets.read`
# Get Transaction
Source: https://docs.crossmint.com/api-reference/wallets/get-transaction
get /2025-06-09/wallets/{walletLocator}/transactions/{transactionId}
Retrieves the current status and details of a specific transaction.
**API scope required**: `wallets:transactions.read`
# Get Wallet Transactions
Source: https://docs.crossmint.com/api-reference/wallets/get-transactions
get /2025-06-09/wallets/{walletLocator}/transactions
Retrieves all transactions associated with the specified wallet. Optionally filter by date range using ISO 8601 format (e.g., 2025-10-27T00:00:00Z).
**API scope required**: `wallets:transactions.read`
# Get Wallet Balance
Source: https://docs.crossmint.com/api-reference/wallets/get-wallet-balance
get /2025-06-09/wallets/{walletLocator}/balances
Get the balance of a wallet for a given chain and currency
**API scope required**: `wallets:balance.read`
## Example Request
This example uses placeholder values. Modify the call with the wallet locator, tokens, and chains you would like to use.
```bash cURL theme={null}
curl --request GET \
--url 'https://staging.crossmint.com/api/2025-06-09/wallets/email:user@example.com:evm/balances?tokens=usdc,eth,pol&chains=base-sepolia,polygon-amoy' \
--header 'X-API-KEY: '
```
```js Node.js theme={null}
const url =
"https://staging.crossmint.com/api/2025-06-09/wallets/email:user@example.com:evm/balances?tokens=usdc,eth,pol&chains=base-sepolia,polygon-amoy";
const options = { method: "GET", headers: { "X-API-KEY": "" }, body: undefined };
try {
const response = await fetch(url, options);
const data = await response.json();
// Process each token's balance across chains
data.forEach((token) => {
console.log(`${token.symbol}${token.name ? ` (${token.name})` : ""}:`);
console.log(` Total amount: ${token.amount} (${token.rawAmount} raw)`);
console.log(` Decimals: ${token.decimals}`);
// Access balance data for each chain
Object.entries(token.chains).forEach(([chainName, chainBalance]) => {
console.log(` ${chainName}: ${chainBalance.amount}`);
console.log(` Locator: ${chainBalance.locator}`);
if (chainBalance.contractAddress) {
console.log(` Contract: ${chainBalance.contractAddress}`);
} else if (chainBalance.mintHash) {
console.log(` Mint Hash: ${chainBalance.mintHash}`);
} else if (chainBalance.contractId) {
console.log(` Contract ID: ${chainBalance.contractId}`);
}
});
console.log();
});
} catch (error) {
console.error(error);
}
```
```python Python theme={null}
import requests
url = "https://staging.crossmint.com/api/2025-06-09/wallets/email:user@example.com:evm/balances"
querystring = { "tokens": "usdc,eth,pol", "chains": "base-sepolia,polygon-amoy" }
headers = { "X-API-KEY": "" }
response = requests.get(url, params=querystring, headers=headers)
data = response.json()
# Process each token's balance across chains
for token in data:
name_part = f" ({token['name']})" if token.get('name') else ""
print(f"{token['symbol']}{name_part}:")
print(f" Total amount: {token['amount']} ({token['rawAmount']} raw)")
print(f" Decimals: {token['decimals']}")
# Access balance data for each chain
for chain_name, chain_balance in token['chains'].items():
print(f" {chain_name}: {chain_balance['amount']}")
print(f" Locator: {chain_balance['locator']}")
if 'contractAddress' in chain_balance:
print(f" Contract: {chain_balance['contractAddress']}")
elif 'mintHash' in chain_balance:
print(f" Mint Hash: {chain_balance['mintHash']}")
elif 'contractId' in chain_balance:
print(f" Contract ID: {chain_balance['contractId']}")
print()
```
## Example Response
The response returns an array of token objects, each containing balance information across the specified chains:
```json theme={null}
[
{
"symbol": "ETH",
"name": "Ethereum",
"decimals": 18,
"amount": "1.5",
"rawAmount": "1500000000000000000",
"chains": {
"base-sepolia": {
"locator": "base-sepolia:eth",
"amount": "1.0",
"rawAmount": "1000000000000000000"
},
"polygon-amoy": {
"locator": "polygon-amoy:eth",
"amount": "0.5",
"rawAmount": "500000000000000000"
}
}
},
{
"symbol": "USDC",
"name": "USD Coin",
"decimals": 6,
"amount": "100.0",
"rawAmount": "100000000",
"chains": {
"base-sepolia": {
"locator": "base-sepolia:0x036CbD53842c5426634e7929541eC2318f3dCF7e",
"amount": "50.0",
"rawAmount": "50000000",
"contractAddress": "0x036CbD53842c5426634e7929541eC2318f3dCF7e"
},
"polygon-amoy": {
"locator": "polygon-amoy:0x41E94Eb019C0762f9Bfcf9Fb1E58725BfB0e7582",
"amount": "50.0",
"rawAmount": "50000000",
"contractAddress": "0x41E94Eb019C0762f9Bfcf9Fb1E58725BfB0e7582"
}
}
},
{
"symbol": "SOL",
"decimals": 9,
"amount": "2.5",
"rawAmount": "2500000000",
"chains": {
"solana": {
"locator": "solana:sol",
"amount": "2.5",
"rawAmount": "2500000000"
}
}
},
{
"symbol": "USDC",
"name": "USD Coin",
"decimals": 6,
"amount": "75.0",
"rawAmount": "75000000",
"chains": {
"solana": {
"locator": "solana:EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
"amount": "75.0",
"rawAmount": "75000000",
"mintHash": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"
}
}
}
]
```
# Get Wallet By Locator
Source: https://docs.crossmint.com/api-reference/wallets/get-wallet-by-locator
get /2025-06-09/wallets/{walletLocator}
Retrieves a wallet by its locator (address or user identifier and wallet type)
**API scope required**: `wallets.read`
# Create Delegated Signer
Source: https://docs.crossmint.com/api-reference/wallets/register-delegated-key
post /2025-06-09/wallets/{walletLocator}/signers
Create a delegated signer for a smart wallet with optional restrictions around permissions and expiry date.
**API scope required**: `wallets.create`
# Transfer Token
Source: https://docs.crossmint.com/api-reference/wallets/transfer-token
post /2025-06-09/wallets/{walletLocator}/tokens/{tokenLocator}/transfers
Sends a token of any type from this wallet to a recipient
# Customization
Source: https://docs.crossmint.com/authentication/customization
Customize the authentication flow and email templates
With Crossmint Auth, you can customize the authentication flow and email templates with your brand's identity.
## Modal Customization
The `CrossmintAuthProvider` component allows you to customize the authentication modal appearance and content.
```tsx theme={null}
By continuing, you agree to our Terms of Service and{" "}
Privacy Policy
}
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.
# Overview
Source: https://docs.crossmint.com/authentication/introduction
A user management solution tightly integrated with all other Crossmint products
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.
## Key Characteristics
Optionally create or link user wallets with all your user accounts, to have a single, unified identity system
across your backend and web3 app.
Authorize calls to your backend services, and to any of Crossmint's products: minting, payments, subscriptions,
etc.
Setup is quick and easy. Launch your app in under 5 minutes.
## Get started
Log-in your first user in less than 5 minutes.
More detailed guide on how to integrate wallets upon user login.
Get and update user information.
Contact our sales team for advanced support.
# 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}
);
}
```
# Quickstart ⚡
Source: https://docs.crossmint.com/authentication/quickstart
Sign up your first user in 5 minutes
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,
CrossmintWalletProvider,
} from "@crossmint/client-sdk-react-ui";
export function Providers({ children }: { children: React.ReactNode }) {
return (
{children}
);
}
```
```tsx create-react-app theme={null}
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import {
CrossmintProvider,
CrossmintAuthProvider,
CrossmintWalletProvider,
} from "@crossmint/client-sdk-react-ui";
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
);
```
```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 (
);
}
```
## 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 client API key on the [API Keys](https://www.crossmint.com/console/projects/apiKeys) page with the API scopes `users.create`, `users.read`, `wallets.read`
3. Replace your test API key with the production key
## Next steps
* Read and update [user information](/authentication/user-profile)
* Use [webhooks](/authentication/webhooks) to get notified when a user signs up
* [Create Smart Wallets on sign up](/wallets/quickstarts/react)
# 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"
});
```
```typescript theme={null}
{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
export const CreateApiKey = ({client, scopes, useJwt}) => {
const scopeStr = (scope, index) => {
if (index === scopes.length - 1) {
return {scope};
} else {
return {scope}, ;
}
};
const localHostInAuthOrigin = client ? "http://localhost:3000" : "";
return
Navigate to the "Integrate" section on the left navigation bar, and ensure you're on the "API Keys" tab.
Within the {client ? "Client-side" : "Server-side"} keys section, click the "Create new key"
button in the top right.
{client ?
On the authorized origins section, enter http://localhost:3000 and click "Add origin".
: ""}
Next, check the scopes labeled {scopes.map((scope, index) => {scopeStr(scope, index)})}.
{useJwt ?
Check the "JWT Auth" box.
: ""}
Finally, create your key and save it for subsequent steps.
;
};
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 theme={null}
npm install @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: SERVER_API_KEY });
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": {
"userId": "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's an example of how you might handle a webhook in a Node.js application:
```javascript theme={null}
// Example webhook handler
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);
});
```
# 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 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
export const ExternalLink = ({href}) => {
return ;
};
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
```
## Command Reference
The Crossmint CLI provides the following commands:
| Command | Description |
| ------------------------------------------------------------------------- | ------------------------------------------------------------- |
| `crossmint login` | 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` | Select a project |
| `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!
```
Note: This command only works for server API keys, as client API keys do not have configurable scopes.
# 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
```
## Example
Start the login process:
```bash theme={null}
crossmint login
```
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 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
```
## Example
Select a project:
```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
```
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.
# About Crossmint
Source: https://docs.crossmint.com/introduction/about-crossmint
Crossmint is an all-in-one platform to integrate wallets, stablecoins, and other blockchain primitives into your product, AI agent, or app. Trusted by the world's largest financial institutions including MoneyGram and Santander Bank, and thousands of start-ups.
## Explore by Product
Create wallets for users, agents, and companies
Move money and onramp
Sell tokens and goods via API and widgets
Mint, distribute, and manage tokens at scale
## Explore by Use Case
Use stablecoins, wallets and Crossmint licenses for programmable money movement
Allow agents to pay for goods and services, and automate payment workflows using n8n
## What makes Crossmint unique?
* **One suite for all your needs**, so 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 ancilliary providers, offering everything you need in a fully integrated platform.
* **Best-in-class UX for your users**: no gas fees, passphrases, transaction approval prompts, or other blockchain gimmicks.
* **Easy-to-use APIs**, which don't presume any 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-code dashboards** for anyone in your team to manage the program - even if they aren't technical.
* **Bank-grade security**: battle-tested and trusted by major financial institutions and Fortune 500 companies like Santander Bank and Nestlé. SOC2 Type II, VASP licensed, and MiCA-ready.
* **No vendor lock-in**: change providers anytime with minimal user disruption.
## More resources
# 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.
# 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.
# Account Verification
Source: https://docs.crossmint.com/introduction/platform/account-verification
Learn how to get your account and collection verified
Enabling certain Crossmint products requires a quick seller KYC and collection review:
| Product | Requirement |
| --------------- | -------------------------------------------------------------------------------------------- |
| Tokenization | No review required |
| Wallets | No review required |
| Fiat Checkout | Quick seller KYC & Collection Review (only for primary sales. Not required for marketplaces) |
| Crypto Checkout | Collection Review required. No KYC required. |
Reviews are only required in production. Staging does not require account or collection review for any product.
Please note that each distinct project you create via the Crossmint Console will require a separate review.
Confirm that your collection is submitted for verification with all of the relevant project details to ensure timely
review. If your collection is still early in development, please refer to our [staging
environment](https://staging.crossmint.com/console) for all testing.
Reviews can take up to 3 business days.
### Account Verification
To enable credit card payments on primaries sellers are required to complete a
quick KYC.
You only have to verify a project once, and can create as many collections
as you wish within that project. New projects require additional verification.
Cross-chain payments do not require account verification. Sellers on marketplaces are also not required to KYC.
Account Verification requires a valid government ID and a selfie. Ensure all photos are well lit but without glare, and uncover your face removing eyewear and pulling long hair behind ears.
Crossmint offers a collection registration API and tools to verify creators and collections at scale. To learn more, [contact us](https://www.crossmint.com/contact/sales).
### Collection Verification
Crossmint has to review and verify all NFT collections before they can be purchased via Crossmint's checkout.
To start the review, open your collection page in the Crossmint console, look for the "Verification
Status" card in the left navbar, and click on "Verify now" in the collection entry. Ensure your collection
meets Crossmint's [content policy](https://www.crossmint.com/content-policy), and that all the
information provided is up to date and truthful. Reviews take up to 72hrs.
Crossmint does not support the sale of securities, investment contracts, or any of the other categories specified on the content policy.
Crossmint offers a collection registration API and tools to verify collections at scale. To learn more, [contact us](https://www.crossmint.com/contact/sales).
# 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.
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/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 the JWT Authentication for your client-side API key, there are additional configurations that must be made. You can choose between Crossmint authentication (easiest), third party auth providers such as Dynamic, Auth0, Stytch, Privy, Firebase, Kinde, or Supabase (medium), or integrating with custom solutions where you generate your own JWTs (advanced).
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** - With an easy to use react SDK you can add user creation and authentication to your application. Crossmint handles the generation of JWTs and you're able to quickly create a robust application with user access controls.
2. **Third Party Authentication providers** - 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.
3. **Custom JWT** - 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 starting a new application and do not have a pre-existing auth provider. You may also want to swap out your existing auth provider if you wish to consolidate your API providers and handle everything with Crossmint.
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/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 |
# 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: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/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 JavaScript 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, or failures
* **Payment processing**: Monitor checkout lifecycle from quote creation to delivery completion
* **NFT 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
```
```py 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);
```
```py 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+ blockchain in one single integration
Refer to the table below to see which chains are supported for which product categories.
| Blockchain | Wallets | Minting | Checkout | Onramp | 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` |
| Boss | - | - | ✅ | - | `boss` | - |
| 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 40+ 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}
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));
```
## 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](/api-reference/minting/collection/get-royalties) | Get royalty information | |
| [Disable Royalties](/api-reference/minting/collection/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/introduction). 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/introduction). 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](/verifiable-credentials/quickstart#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.
# 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.
# React SDK Reference
Source: https://docs.crossmint.com/payments/advanced/component-properties
Understand the properties available on the Checkout
## Common Properties
These properties are shared between both the Embedded and Hosted checkout components.
Specifies the NFTs to purchase. Can be a single item or array of items.
Collection identifier in one of these formats:
* `crossmint:<_YOUR_COLLECTION_ID_>[:_TEMPLATE_ID_]` - For collections created in Crossmint Console
* Template ID is optional and requires template minting to be enabled
* If template minting is disabled and a template ID is provided, the mint will select a template randomly
* `:` - For collections using direct contract addresses (e.g., `polygon-amoy:0xF3d2d7b5666f579DcE385b2d53c54AB1b09Ef563`)
When using template minting, append the templateId to the collection locator: `crossmint:collectionId:templateId`.
Template minting must be enabled in the Crossmint Console or the mint will select a template randomly.
See the [Mint to Specific Template guide](/payments/advanced/mint-to-specific-template) for details on enabling template minting and error handling.
For secondary sales (buying existing NFTs). Token identifier in one of these formats:
* EVM chains: `::`
* Solana: `:`
Using `tokenLocator` doesn't require registering the collection in Crossmint Console.Use either `collectionLocator` (for primary sales) or `tokenLocator` (for secondary sales), not both.
Arguments passed to your contract's mint function:
```solidity Solidity theme={null}
// Example mint function
function mint(
address to, // Auto-filled by Crossmint
uint256 quantity,
uint256 totalPrice
) public payable {
// ...
}
```
Parameter names must match your contract's function arguments exactly.Do not pass the recipient argument (e.g. `to`) in callData. Crossmint handles this automatically.
Configuration for payment methods.
Crypto payment settings:
```tsx theme={null}
{
enabled: true, // Enable/disable crypto payments
defaultChain: "ethereum" | "polygon" | "solana", // Optional: preferred blockchain
defaultCurrency: "eth" | "matic" | "sol" | 'usdc' // Optional: preferred currency
}
```
Card & wallet payment settings:
```tsx theme={null}
{
enabled: true, // Enable/disable fiat payments
defaultCurrency: "usd" | "eur" | "gbp", // Optional: preferred currency
}
```
Embedded Checkout supports additional payment method customization. See our [Embedded Checkout payment methods guide](/payments/embedded/guides/payment-methods) for details.
Optional email address where purchase receipt will be sent
Sets the default payment tab: `fiat` or `crypto`
Delivery details for the NFTs. You must specify either email OR wallet address, not both:
NFTs delivered to user's Crossmint wallet linked to this email
NFTs delivered directly to this blockchain address
When not provided, user will be prompted during checkout.
Sets the checkout interface language.
`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`
## Specific Properties & Hooks
The hosted checkout button is only available for React applications. For full customization options, please use our [Headless Checkout](/payments/headless/overview).
The following properties are available for the `CrossmintHostedCheckout` component:
Customization options for the hosted checkout. See our [UI Customization guide](/payments/pay-button/guides/ui-customization) for complete details.
**Notable appearance options:**
* Display mode: `display: "popup" | "new-tab"` (default: "popup")
* Background overlay: `overlay.enabled: false` to disable the gray background
* Button theme: `theme.button: "light" | "dark" | "crossmint"` (default: "light")
* Checkout theme: `theme.checkout: "light" | "dark"` (default: "light")
The embedded checkout is only available for React applications. For full customization options, please use our [Headless Checkout](/payments/headless/overview).
The following properties are available for the `CrossmintEmbeddedCheckout` component:
Extends the common payment configuration with additional options.
Extended fiat payment settings:
```tsx theme={null}
{
enabled: true, // Enable/disable fiat payments
defaultCurrency: "usd" | "eur" | "gbp", // Optional: preferred currency
allowedMethods: {
card: boolean, // Credit/debit cards
applePay: boolean, // Apple Pay
googlePay: boolean // Google Pay
}
}
```
Customization options for the checkout UI. See our [UI Customization guide](/payments/embedded/guides/ui-customization) for complete details.
**Notable appearance options:**
* Hide destination input: `rules.DestinationInput.display: "hidden"`
* Hide receipt email input: `rules.ReceiptEmailInput.display: "hidden"`
If inputs are hidden, you must provide the corresponding values via recipient/payment props.
The following React hooks are available for use with the Embedded Checkout:
The `useCrossmintCheckout` hook is used to access the current order and checkout state.
All Crossmint hooks must be used within components wrapped by both `CrossmintProvider` and `CrossmintCheckoutProvider`:
```tsx theme={null}
{/* Hooks can be used here */}
```
The following properties are available from the `useCrossmintCheckout` hook:
The current order object containing all order details.
Unique identifier for the order
Current phase of the order: `quote` | `payment` | `delivery` | `completed`
Array of items being purchased.
Blockchain network for the NFT
Number of NFTs being purchased
```tsx theme={null}
{
name: string;
description: string;
imageUrl: string;
collection: {
name: string;
description: string;
imageUrl: string;
};
}
```
```tsx theme={null}
{
status: "valid" | "item-unavailable" | "expired" | "requires-recipient";
charges: {
unit: { currency: string; amount: string; };
gas: { currency: string; amount: string; };
};
totalPrice: { currency: string; amount: string; };
}
```
NFT delivery status and details:
```tsx theme={null}
// When completed:
{
status: "completed";
txId: string;
tokens: [{
locator: string;
contractAddress: string;
tokenId: string;
mintHash: string;
}];
recipient: {
walletAddress: string;
locator: string;
email: string;
};
}
// When in progress:
{
status: "awaiting-payment" | "in-progress" | "failed";
recipient: {
walletAddress: string;
locator: string;
email: string;
};
}
```
Order quote status and pricing:
```tsx theme={null}
{
status: "valid" | "expired" | "requires-recipient" | "all-line-items-unavailable";
quotedAt: string;
expiresAt: string;
totalPrice: {
currency: string;
amount: string;
};
}
```
# 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](/docs.crossmint.com/payments/advanced/component-properties#mintconfig), or your [payment order objects.](/docs.crossmint.com/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](/docs.crossmint.com/payments/embedded/overview) or [headless checkout](/docs.crossmint.com/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}
{
"isClaimed": false,
"claimUrl": "https://www.crossmint.com/claim/collection/";
"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}
{
"isClaimed": true,
"verificationStatus": {
"seller": {
"status": "verified"
},
"collection": {
"status": "verified"
},
},
"claimUrl": null,
}
```
***
## 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
Currently, memecoin checkout only supports the Solana network.
The token locator follows this format:
```
network:tokenAddress
```
Example:
* TRUMP: `solana:6p6xgHyF7AeE6TZkSmFsko444wqoP15icUSqi2jfGiPN`
## 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, please contact our support team
or your account manager.
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.
{item.usdxm ? (
{item.usdxm}
) : (
Not available
)}
))}
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",
"txId": "0x2e69f11dae7869b92e3d5eaf4cadd50c48b5c6803d1232815f979d744521ad4c",
"contractAddress": "0xE04Cf294985282Ddc088E6433c064cfB85eD9EdA",
"tokenIds": ["3"],
"recipient": {
"walletAddress": "0x..."
}
}
}
```
### 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:
## Prerequisites
1. **HTTPS Hosting**: Your site must be hosted on HTTPS to ensure secure transactions.
2. **Domain Validation**: Provide your domain to our support team for validation.
## Download the Apple Validation File
Before proceeding, download the Apple Developer Merchant ID Domain Association File:
[Download the Apple Developer Merchant ID Domain Association File](https://stripe.com/files/apple-pay/apple-developer-merchantid-domain-association)
## Setup Instructions
### Step 1: Host the Apple Developer Merchant ID Domain Association File
You need to host the following endpoint on your server and return the Apple `.txt` validation file as plain text:
```
https://[your-domain]/.well-known/apple-developer-merchantid-domain-association
```
### Example Setup Using Next.js App Router
**Option 1: Using the Public Folder**
1. **Create a Public Folder**: In your Next.js project, create the `.well-known` folder inside your public directory.
2. **Add the Validation File**: Paste the Apple verification txt with no extension in `public/.well-known/`.
Filename: `apple-developer-merchantid-domain-association`
**Option 2: Using a Route**
1. **Create the Route Structure**: In your Next.js project, create a folder at:
Path: `app/.well-known/apple-developer-merchantid-domain-association/`
2. **Add a `route.ts` File**: Inside the folder, create 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("TXT-CONTENT", {
headers: {
"Content-Type": "text/plain",
},
});
}
```
Replace `'TXT-CONTENT'` with the actual content of your Apple validation file.
### Example Setup Using Next.js Page Router
1. **Create a Public Folder**: In your Next.js project, create the `.well-known` folder inside your public directory.
2. **Add the Validation File**: Paste the Apple verification txt with no extension in `public/.well-known/`.
Filename: `apple-developer-merchantid-domain-association`
### Example Setup Using Vite
1. **Create a Public Folder**: In your Vite project, create the `.well-known` folder inside your public directory.
2. **Add the Validation File**: Paste the Apple verification txt with no extension in `public/.well-known/`.
Filename: `apple-developer-merchantid-domain-association`
### Step 2: Test Apple Pay
Test Apple Pay on Safari (macOS) or any iOS device once setup is complete.
## Local Testing
We recommend using [ngrok](https://ngrok.com/) to test your setup locally. Ngrok allows you to expose your local server to the internet securely. If possible, use a fixed domain with ngrok for consistent testing.
***
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)
# 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
Embedded checkout in production requires enablement by the Customer Success Engineering team. [Contact Crossmint's
team](https://www.crossmint.com/contact/sales) to get access.
***
### 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 the embedded checkout in action on the [Crossmint Playground](https://playground.crossmint.com/checkout/embedded).
## When is the Embedded Checkout the best fit?
* **You want to insert the checkout into your site**
* **You want high level of control over the UI/UX**
## Get Started
Start selling digital assets in 5 minutes.
Contact our sales team for advanced support.
## Advanced Topics
# Pay with Card - Memecoins
Source: https://docs.crossmint.com/payments/embedded/quickstarts/credit-card-memecoin
Create a fully customized embedded memecoin checkout experience that accepts credit cards
export const CreateApiKey = ({client, scopes, useJwt}) => {
const scopeStr = (scope, index) => {
if (index === scopes.length - 1) {
return {scope};
} else {
return {scope}, ;
}
};
const localHostInAuthOrigin = client ? "http://localhost:3000" : "";
return
Navigate to the "Integrate" section on the left navigation bar, and ensure you're on the "API Keys" tab.
Within the {client ? "Client-side" : "Server-side"} keys section, click the "Create new key"
button in the top right.
{client ?
On the authorized origins section, enter http://localhost:3000 and click "Add origin".
: ""}
Next, check the scopes labeled {scopes.map((scope, index) => {scopeStr(scope, index)})}.
{useJwt ?
Check the "JWT Auth" box.
: ""}
Finally, create your key and save it for subsequent steps.
;
};
## 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`: Required for delivering payment receipts
* `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)
Memecoins are testable in staging using the xmeme token on Solana devnet
(`7EivYFyNfgGj8xbUymR7J4LuxUHLKRzpLaERHLvi7Dgu`). For production launch with live memecoin tokens, [contact our
sales team](https://www.crossmint.com/contact/sales).
### 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 our sales team](https://www.crossmint.com/contact/sales).
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 30 seconds), the embedded checkout will automatically refresh the quote. You can
customize this behavior using the checkout hooks.
# Crossmint Checkout React Native SDK Quickstart
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 **required** for delivering payment receipts to customers.
```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
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](/payments/headless/guides/onramp), [memecoins](/payments/headless/quickstarts/credit-card-memecoin), 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/advanced/selling-multiple-nfts) -
[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": "stripe-payment-element",
"currency": "eur", // If collection is priced in USD, price returned for quote will be in EUR
"payerAddress": ""
},
"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:
```shell 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 JavaScript 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).
```shell 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 JavaScript 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.
For a multi-item order, as long as at least one item was fulfilled successfully the order is considered successful. If all items fail to deliver in an order then the order status is failed.
To determine the order completion status simply check the `order.phase` property. This will be set to `completed` for a successful order or `failed` if all items were unable to be delivered.
For reference on all statuses (quote, payment, delivery), see the [Status Codes](/payments/headless/guides/status-codes) page.
For a multi-item order you'll need to loop through the `lineItems` array returned in the get order response to
ensure the `delivery.status` is `completed` for each item.
### 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. Refer to the [wallet UI components](/wallets/advanced/wallet-ui-components) page for some details on how to accomplish this.
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 `payment.preparation` object that your
application uses to render the Stripe payment element.
Use the `stripePublishableKey` and `stripeClientSecret` returned in the API response to render the credit card
checkout form.
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 Stripe Payment Element
When the order is ready to accept payment, the API response will include an `order.payment.preparation` object, which contains two important properties to render the payment element. These properties are named: `stripePublishableKey` and `stripeClientSecret`. You can use these with the [Stripe Payment Element](https://docs.stripe.com/payments/payment-element) package 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": "stripe-payment-element",
"currency": "usd",
"preparation": {
"stripeClientSecret": "pi_returned_secret_",
"stripePublishableKey": "pk_test_publishable_key_value"
}
}
}
}
```
### Handle Payment Confirmation
### 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": "stripe-payment-element",
"currency": "usd"
},
"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": "stripe-payment-element",
"currency": "usd"
},
"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 can be `stripe-payment-element` or `checkoutcom-flow` 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": "stripe-payment-element",
"currency": "usd"
}
}
```
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).
### Accepting Credit Cards in Other Platforms
Part of what makes headless checkout so powerful is that it opens the possibility of accepting credit card payments with Crossmint outside of a browser environment by integrating
any of the currently supported PSP (**Stripe**, **Checkout.com**). You can take a look to [Pay With Card - NFTs](https://docs.crossmint.com/payments/headless/quickstarts/credit-card-nft#3-render-the-stripe-payment-element)
and [Pay With Card - memecoins](https://docs.crossmint.com/payments/headless/quickstarts/credit-card-memecoin-cko#render-the-checkout-com-flow-component) docs for more details on how to integrate
Crossmint checkout support in native mobile apps and more.
# 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
Headless checkout in production requires enablement by the Customer Success Engineering team. [Contact Crossmint's
team](https://www.crossmint.com/contact/sales) to get access.
***
### 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" // Required 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" // Required 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" // Required 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
export const CreateApiKey = ({client, scopes, useJwt}) => {
const scopeStr = (scope, index) => {
if (index === scopes.length - 1) {
return {scope};
} else {
return {scope}, ;
}
};
const localHostInAuthOrigin = client ? "http://localhost:3000" : "";
return
Navigate to the "Integrate" section on the left navigation bar, and ensure you're on the "API Keys" tab.
Within the {client ? "Client-side" : "Server-side"} keys section, click the "Create new key"
button in the top right.
{client ?
On the authorized origins section, enter http://localhost:3000 and click "Add origin".
: ""}
Next, check the scopes labeled {scopes.map((scope, index) => {scopeStr(scope, index)})}.
{useJwt ?
Check the "JWT Auth" box.
: ""}
Finally, create your key and save it for subsequent steps.
;
};
## Introduction
This guide will show you how to accept credit card payments using Crossmint's Headless Checkout API for memecoin sales with [Checkout.com](https://www.checkout.com/docs) as the payment provider. You'll learn how to:
* Set up credit card payments for Solana memecoin purchases in JavaScript
* Implement a checkout UI using Checkout.com's Flow component
* 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 our sales team.
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 JavaScript 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: "checkoutcom-flow", // Using Checkout.com as the payment processor
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": "checkoutcom-flow",
"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": "checkoutcom-flow",
"currency": "usd",
"preparation": {
"checkoutcomPaymentSession": {
"id": "string",
"payment_session_secret": "pss_57d30246-c936-42ed-8bbd-b231da1b979e",
"payment_session_token": "mFzZTY0:eyJpZCI6InBzXzJ0OUEzNFJZaHVVc0xCYkt3TzI5NFN1UVFFdyIsImVudGl0eV9pZCI6Im1FzZTY0:eyJpZCI6InBzXzJ0OUEzNFJZaHVVc0xCYkt3TzI5NFN1UVFFdyIsImVudGl0eV9pZCI6Im..."
},
"checkoutcomPublicKey": "pk_test_51KIdg4..."
},
"receiptEmail": "test@example.com"
}
}
}
```
Note the following parameters in the request body:
* `maxSlippageBps`: Optional, or else default autogenerated slippage will be applied
* `receiptEmail`: Required for credit card payments to deliver receipt
* `executionParameters.mode`: The execution method for the order. "exact-out" is for NFTs, "exact-in" is for fungible tokens
### Render the Checkout.com Flow Component
After creating an order, you'll need to render the Checkout.com Flow component to collect payment information. The Flow component is a pre-built UI that handles the payment collection process.
**Reference Documentation:**
* [Checkout.com docs for desktop](https://www.checkout.com/docs/payments/accept-payments/accept-a-payment-on-your-website/get-started-with-flow)
* [Checkout.com docs for mobile](https://www.checkout.com/docs/payments/accept-payments/accept-a-payment-on-your-mobile-app/get-started-with-flow-for-mobile)
```javascript theme={null}
import { useEffect, useState } from 'react';
import Script from 'next/script';
import Image from 'next/image';
import { Spinner } from '@/components/ui/spinner';
export function CheckoutComEmbedded({ embeddedCheckoutParameters }) {
const { createOrder, order } = useOrder();
const [isCheckoutReady, setIsCheckoutReady] = useState(false);
const [isScriptLoaded, setIsScriptLoaded] = useState(false);
useEffect(() => {
async function initiateOrder() {
try {
await createOrder(embeddedCheckoutParameters);
} catch (error) {
console.error("Failed to create order:", error);
}
}
initiateOrder();
}, [embeddedCheckoutParameters, createOrder]);
useEffect(() => {
if (order == null) {
return;
}
console.log("order", order);
}, [order]);
useEffect(() => {
if (order == null) {
return;
}
const initializeCheckout = async () => {
try {
if (typeof window.CheckoutWebComponents !== 'function') {
console.error('CheckoutWebComponents not loaded properly');
return;
}
const checkout = await window.CheckoutWebComponents({
appearance: {
colorBorder: "#FFFFFF",
colorAction: '#060735',
borderRadius: ["8px", "50px"],
},
publicKey: order.payment.preparation.checkoutcomPublicKey,
environment: "sandbox", // Change to "live" for production
locale: "en-US",
paymentSession: order.payment.preparation.checkoutcomPaymentSession,
cors: {
mode: 'no-cors',
credentials: 'same-origin'
},
onReady: () => {
console.log("Flow is ready");
setIsCheckoutReady(true);
}, // checkout.com takes a second to load, so need to wait for it to render
onPaymentCompleted: (component, paymentResponse) => {
console.log("Payment completed with ID:", paymentResponse.id);
},
onChange: (component) => {
console.log(`Component ${component.type} validity changed:`, component.isValid());
},
onError: (component, error) => {
console.error("Payment error:", error, "Component:", component.type);
},
});
const flowComponent = checkout.create("flow");
const container = document.getElementById("flow-container");
if (container) {
flowComponent.mount(container);
}
} catch (error) {
console.error("Error initializing checkout:", error);
}
};
// Initialize checkout when the script is loaded and payment session exists
const scriptElement = document.querySelector('script[src*="checkout-web-components"]');
if (scriptElement) {
initializeCheckout();
}
}, [order]);
if (!order) {
return (