---
title: "Find out why a payment was declined"
description: "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

<CodeGroup>
```bash title="cURL"
curl --request GET \
     --url https://api.gravv.xyz/v1/cards/a14ec356-f33d-4384-ba68-4d9bfa19765b \
     --header 'Api-Key: <API_KEY>'
```
```javascript title="Node"
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());
```
```python title="Python"
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())
```
</CodeGroup>

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

### 2. Read the balance

<CodeGroup>
```bash title="cURL"
curl --request GET \
     --url https://api.gravv.xyz/v1/cards/a14ec356-f33d-4384-ba68-4d9bfa19765b/balance \
     --header 'Api-Key: <API_KEY>'
```
```javascript title="Node"
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());
```
```python title="Python"
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())
```
</CodeGroup>

`balance` must cover the amount.

### 3. Match what you see

| What you see | Likely cause | Fix |
| :-- | :-- | :-- |
| `status` `freeze` | The card is frozen | [Unfreeze it](/recipes/cards/freeze-or-block-a-lost-card) |
| `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](/recipes/cards/top-up-a-card) |
| `daily_transaction_limit` `0` | You sent an update without `limit` | [Set the limit](/recipes/cards/change-a-cards-daily-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](/api-reference/webhooks/post-v1-webhooks-event-send-id). Gravv does not sign a resend.

## Reference

- [Card decline reasons](/platform/cards/card-decline-reasons)
- [Check card balance](/platform/cards/check-card-balance)
