Find out why a payment was declined

The API does not return a decline reason today. Work out the most likely cause from the card's status, limit and balance.

Use this recipe to work out the most likely reason a card payment was declined. The API does not return a decline reason today, so you check the card's status, limit and balance.

Who it is for. A support agent at any card issuer answering "my card was declined".

Status. Decline reasons through the API are Not yet available. The checks below find the most likely cause.

Money example

Ada tries to pay 45 USD and it is declined.

  1. Her card holds 30 USD. You top it up with 20 USD, and she pays.
  2. Or her card holds 200 USD, but its daily limit reads 0 because you sent an update without limit. You set the limit back to 300, and she pays.

Before you start

You need the card id. Read it from event_data.card.id in the cards.transaction.payment webhook, or from GET /v1/cards?customer_id=….

Find out what is required

A declined payment arrives as cards.transaction.payment with transaction.status failed, and the event has no reason field. For each payment, Gravv sends pending, then completed or failed.

Steps

1. Read the card

curl --request GET \
 --url https://api.gravv.xyz/v1/cards/a14ec356-f33d-4384-ba68-4d9bfa19765b \
 --header 'Api-Key: <API_KEY>'
const response = await fetch("https://api.gravv.xyz/v1/cards/a14ec356-f33d-4384-ba68-4d9bfa19765b", {
method: "GET",
headers: {
"Api-Key": process.env.GRAVV_API_KEY,
},
});
console.log(response.status, await response.json());
import os

import requests

response = requests.get(
"https://api.gravv.xyz/v1/cards/a14ec356-f33d-4384-ba68-4d9bfa19765b",
headers={
    "Api-Key": os.environ["GRAVV_API_KEY"],
},
)
print(response.status_code, response.json())

Check that status is active, card_type is virtual, and spending_controls.daily_transaction_limit is above the amount.

2. Read the balance

curl --request GET \
 --url https://api.gravv.xyz/v1/cards/a14ec356-f33d-4384-ba68-4d9bfa19765b/balance \
 --header 'Api-Key: <API_KEY>'
const response = await fetch("https://api.gravv.xyz/v1/cards/a14ec356-f33d-4384-ba68-4d9bfa19765b/balance", {
method: "GET",
headers: {
"Api-Key": process.env.GRAVV_API_KEY,
},
});
console.log(response.status, await response.json());
import os

import requests

response = requests.get(
"https://api.gravv.xyz/v1/cards/a14ec356-f33d-4384-ba68-4d9bfa19765b/balance",
headers={
    "Api-Key": os.environ["GRAVV_API_KEY"],
},
)
print(response.status_code, response.json())

balance must cover the amount.

3. Match what you see

What you see Likely cause Fix
status freeze The card is frozen Unfreeze it
status blocked The card is closed Issue a new card
status inactive, or card_type physical A physical card cannot spend today Use a virtual card
balance below the amount The card does not hold enough money Top up the card
daily_transaction_limit 0 You sent an update without limit Set the limit
Today's payments add up past the limit The daily limit is reached Wait 24 hours, or raise the limit
Everything above is fine A merchant rule, a card network rule or a fraud rule Contact support with the card id, amount and time

Tell the cardholder that this is the most likely cause. None of these checks proves it.

What can go wrong

Response Why Fix
400 card does not exist The card id is wrong Check the id
Several failed payments in a row, and all checks pass The cause is not visible through the API Contact support

Webhooks you will receive

You receive cards.transaction.payment with transaction.status pending, completed or failed, transaction.amount (a string in dollars) and transaction.transaction_type card_payment.

Testing in sandbox

Spend and decline simulation through the API is Not yet available. To test your handler, post a recorded cards.transaction.payment payload to your own endpoint, or resend a stored event. Gravv does not sign a resend.

Reference

Was this page helpful?