Card
Collect a card payment with a hosted payment link, as a one-time charge or a saved card for recurring charges.
This page shows how to collect payments from customers with a credit or debit card. There are two ways to charge a card:
- One-time charge. Gravv generates a payment link for a single payment. You redirect the customer to the link, and Gravv hosts the checkout page.
- Recurring charge. Gravv generates a payment link for the first payment and saves the card if the customer agrees. Get the saved card's
card_tokenfrom Get saved cards and use it to charge the customer again. The customer does not re-enter their card details.
Gravv always charges card collections in US dollars. The amount is a USD amount, whatever currency you send. The customer record must have a phone number and a date of birth. The customer's address country decides whether cards are available. The request has no country field.
One-time charge
Send an Initiate a collection request call with card in source.methods. Send amount as a string with at most 2 decimal places. Gravv returns a payment_link in the response. Redirect your customer to this link to complete the payment:
curl --request POST \
--url https://api.gravv.xyz/v1/collections \
--header 'Api-Key: <API_KEY>' \
--header 'Idempotency-Key: 5e6f7a8b-9c0d-4e1f-8a2b-3c4d5e6f7a01' \
--header 'accept: application/json' \
--header 'content-type: application/json' \
--data '
{
"amount": "50.00",
"currency": "USD",
"customer_id": "04dfb2e5-1274-4214-b5fd-3415fde7dc17",
"client_customer_id": "checkers_user",
"client_reference": "9d8184a1-4a19-b74d-4797-9896aed01332",
"source": {
"source_type": "external",
"methods": [
"card"
]
},
"destination": {
"id": "bccb74d8-9715-478d-bcac-9d8184a19530",
"destination_type": "internal_crypto_wallet"
}
}
'
The response looks like this:
{
"data": {
"amount": "50.00",
"asset": "USDC",
"client_reference": "9d8184a1-4a19-b74d-4797-9896aed01332",
"converted_amount": "0.00",
"country": "NG",
"currency": "USD",
"network": "polygon",
"new_onramp_status": "initialized",
"onramp_status": "initialized",
"payment_link": "https://<checkout-host>/payment/07b655cc-3dbd-4f6c-a16c-4657946d2e08",
"rate": "0.00",
"status": "created",
"status_transition_deadline": "2026-10-31T23:59:59Z",
"transaction_id": "07b655cc-3dbd-4f6c-a16c-4657946d2e08"
},
"error": null
}
Redirect the customer to payment_link. They enter their card details on Gravv's checkout page and complete the payment there. Gravv abandons a checkout that has no activity for 24 hours.
Recurring charge
A recurring charge uses the payment intent endpoints:
- Your backend creates a payment intent with
save_cardset totrueand receives apayment_link. - You redirect the customer to that link to complete the first payment.
- If the customer agrees, Gravv saves the card. You can charge the saved card once that first payment settles.
Create a payment intent
Call the Create payment intent endpoint from your backend. Gravv looks up saved cards by client_customer_id, so use the same value for a customer every time:
curl --request POST \
--url https://api.gravv.xyz/v1/collections/cards/payment-intents \
--header 'Api-Key: <API_KEY>' \
--header 'Idempotency-Key: 5e6f7a8b-9c0d-4e1f-8a2b-3c4d5e6f7a02' \
--header 'accept: application/json' \
--header 'content-type: application/json' \
--data '
{
"customer_id": "f827e428-c497-4b21-8855-99d978909c28",
"client_customer_id": "merchant-user-123",
"amount": "3.50",
"currency": "USD",
"client_reference": "order-abc-123",
"save_card": true,
"destination": {
"destination_type": "internal_crypto_wallet",
"id": "b628ff35-429b-48f7-bcf0-6a49df106fc6"
},
"source": {
"source_type": "external",
"methods": ["card"]
}
}
'
The response contains a payment_link. Redirect the customer to this link to complete the payment. The link contains a secret for this payment, so send it only to the customer:
{
"data": {
"intent_id": "pi_429f578b-675a-4b68-abe3-856cd976257f",
"client_reference": "order-abc-123",
"status": "created",
"amount": "3.50",
"currency": "USD",
"payment_link": "https://<checkout-host>/pi_429f578b-675a-4b68-abe3-856cd976257f?session=…",
"created_at": "2026-10-12T10:00:00Z",
"expires_at": "2026-10-13T10:00:00Z"
},
"error": null
}
A payment intent expires 24 hours after you create it, and it allows at most 5 charge attempts. Its status is one of created, tokenized, requires_payment_method, requires_confirmation, requires_action, confirmed, processing, succeeded, failed, cancelled or expired. Create a new intent after a hard decline, after 5 attempts, or when the intent expires.
Charge a saved card
To charge the customer without redirecting them again:
- Create a new payment intent without
card_token. - Call the Charge card endpoint with the saved
card_token, and with the new intent'sintent_idaspayment_intent_id,pi_prefix included.
Billing fields are optional. Any field you omit falls back to the saved card's billing record, then to the customer record:
curl --request POST \
--url https://api.gravv.xyz/v1/collections/cards/payment-intents/charge \
--header 'Api-Key: <API_KEY>' \
--header 'Idempotency-Key: 5e6f7a8b-9c0d-4e1f-8a2b-3c4d5e6f7a03' \
--header 'accept: application/json' \
--header 'content-type: application/json' \
--data '
{
"payment_intent_id": "pi_429f578b-675a-4b68-abe3-856cd976257f",
"card_token": "tokn_80aebb6ea2594a74bc",
"first_name": "Jane",
"last_name": "Doe",
"email": "jane@example.com",
"billing_address1": "123 Market St",
"billing_city": "San Francisco",
"billing_state": "CA",
"billing_zip": "94105",
"billing_country": "US"
}
'
A successful charge returns status succeeded. When the issuer asks for 3D Secure, status is requires_action and the response has next_action_url. Send the customer to that URL to finish the payment. A declined charge returns HTTP 400 with a sentence such as The card has insufficient funds: payment declined.
{
"data": {
"transaction_id": "90950347-d40b-4ab9-aa77-f1882750477c",
"payment_intent_id": "pi_429f578b-675a-4b68-abe3-856cd976257f",
"status": "succeeded"
},
"error": null
}
You can charge a saved card at most 5 times in 24 hours, and each charge can be at most 3 times the card's first payment.
You can also make a recurring charge without a separate call to the Charge card endpoint. Pass the card_token in the card_token field when you call Create payment intent. Gravv charges the card at once. The response is HTTP 200 whatever the outcome, so check data.status:
data.status |
Meaning |
|---|---|
processing |
The charge went through |
requires_action |
The issuer asks for 3D Secure |
charge_failed |
The charge failed. message has the reason |
Manage saved cards
Gravv stores card tokens on the customer record. Use these endpoints to read or remove them.
Get saved cards
Call the Get saved cards endpoint to list a customer's saved cards and their card_token values. customer_id is required:
curl --request GET \
--url 'https://api.gravv.xyz/v1/collections/cards?customer_id=f827e428-c497-4b21-8855-99d978909c28' \
--header 'Api-Key: <API_KEY>'
data is an array of saved cards. You can charge only a card with is_active true. A card is inactive until its first payment settles.
Get saved card
Call the Get saved card endpoint to retrieve a single card by the id from the list:
curl --request GET \
--url https://api.gravv.xyz/v1/collections/cards/a1b2c3d4-e5f6-7890-abcd-ef1234567890 \
--header 'Api-Key: <API_KEY>'
The response looks like this. Ignore is_active in this response. It always reads false, and the endpoint returns only active cards:
{
"data": {
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"card_token": "tokn_80aebb6ea2594a74bc",
"card_last4": "1111",
"card_brand": "visa",
"card_exp_month": "12",
"card_exp_year": "2028",
"is_default": true,
"is_active": false,
"is_expiring_soon": false,
"created_at": "2026-10-01T10:00:00Z"
},
"error": null
}
is_expiring_soon is true when the card expires within 30 days.
Delete a saved card
Call the Delete saved card endpoint to remove a saved card. Send a JSON body with the card owner's customer_id and the card_token. Gravv deletes the card that card_token names. A request without a body fails with EOF:
curl --request DELETE \
--url https://api.gravv.xyz/v1/collections/cards/a1b2c3d4-e5f6-7890-abcd-ef1234567890 \
--header 'Api-Key: <API_KEY>' \
--header 'content-type: application/json' \
--data '
{
"customer_id": "f827e428-c497-4b21-8855-99d978909c28",
"card_token": "tokn_80aebb6ea2594a74bc"
}
'
The response looks like this. A token that matches no active card returns card already deleted:
{
"data": {
"success": true,
"message": "card deleted successfully"
},
"error": null
}