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_token from 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:

  1. Your backend creates a payment intent with save_card set to true and receives a payment_link.
  2. You redirect the customer to that link to complete the first payment.
  3. 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:

  1. Create a new payment intent without card_token.
  2. Call the Charge card endpoint with the saved card_token, and with the new intent's intent_id as payment_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
}
Was this page helpful?