curl -X POST "https://api-sandbox.paywithsoap.com/api/v1/charges/ch_pQsQ4kz3Af6Mb9rCupnWj6VFzxJsmkYK/refund" \
-H "Authorization: Bearer YOUR_API_KEY"
require 'net/http'
require 'uri'
charge_id = 'ch_pQsQ4kz3Af6Mb9rCupnWj6VFzxJsmkYK'
uri = URI.parse("https://api-sandbox.paywithsoap.com/api/v1/charges/#{charge_id}/refund")
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Post.new(uri.request_uri)
request['Authorization'] = 'Bearer YOUR_API_KEY'
response = http.request(request)
puts response.body
const axios = require('axios');
const refundCharge = async (chargeId) => {
try {
const response = await axios.post(
`https://api-sandbox.paywithsoap.com/api/v1/charges/${chargeId}/refund`,
{},
{
headers: {
'Authorization': 'Bearer YOUR_API_KEY'
}
}
);
console.log(response.data);
} catch (error) {
console.error(error);
}
};
refundCharge('ch_pQsQ4kz3Af6Mb9rCupnWj6VFzxJsmkYK');
{
"error": "Refund failed",
"responsetext": "Refund amount may not exceed the transaction balance"
}
{
"error": "Unable to refund charge.",
"hint": "Please verify the charge_id is correct. \n charge_id: ch_pQsQ4kz3Af6Mb9rCupnWj6VFzxJsmkYK"
}
Refund a Charge
Refund a succeeded card deposit in full (NMI and Rapyd)
curl -X POST "https://api-sandbox.paywithsoap.com/api/v1/charges/ch_pQsQ4kz3Af6Mb9rCupnWj6VFzxJsmkYK/refund" \
-H "Authorization: Bearer YOUR_API_KEY"
require 'net/http'
require 'uri'
charge_id = 'ch_pQsQ4kz3Af6Mb9rCupnWj6VFzxJsmkYK'
uri = URI.parse("https://api-sandbox.paywithsoap.com/api/v1/charges/#{charge_id}/refund")
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Post.new(uri.request_uri)
request['Authorization'] = 'Bearer YOUR_API_KEY'
response = http.request(request)
puts response.body
const axios = require('axios');
const refundCharge = async (chargeId) => {
try {
const response = await axios.post(
`https://api-sandbox.paywithsoap.com/api/v1/charges/${chargeId}/refund`,
{},
{
headers: {
'Authorization': 'Bearer YOUR_API_KEY'
}
}
);
console.log(response.data);
} catch (error) {
console.error(error);
}
};
refundCharge('ch_pQsQ4kz3Af6Mb9rCupnWj6VFzxJsmkYK');
{
"error": "Refund failed",
"responsetext": "Refund amount may not exceed the transaction balance"
}
{
"error": "Unable to refund charge.",
"hint": "Please verify the charge_id is correct. \n charge_id: ch_pQsQ4kz3Af6Mb9rCupnWj6VFzxJsmkYK"
}
"Only credit charges can be refunded.", and charges on other processors return "We cannot refund this transaction through this processor at the moment." — both as a 422.422 with "We cannot refund this transaction through this processor at the moment.", whatever the charge’s age. To exercise your checkout.refunded handler in sandbox, pay the test amount $1.08 — see Sandbox Test Data. Bank-rail deposits have no refund path in any environment; return money to a bank customer with a fixed-amount withdrawal instead.Behavior
- The request takes no body — the full charge amount is always refunded.
- On success the charge transitions to the terminal
refundedstatus and the response is the same charge object returned by Retrieve a Charge, withstatus: "refunded". - A charge can only be refunded once; a second attempt fails because the charge is no longer in the
succeededstatus. - If you subscribe to the opt-in
checkout.refundedwebhook, it fires when the charge transitions.
Settlement timing
Once a card charge is recorded assucceeded it can be refunded whether or not the daily settlement batch has run, so same-day refunds work. If the processor declines a refund for any reason, the API returns a 422 whose responsetext carries the processor’s decline reason.
curl -X POST "https://api-sandbox.paywithsoap.com/api/v1/charges/ch_pQsQ4kz3Af6Mb9rCupnWj6VFzxJsmkYK/refund" \
-H "Authorization: Bearer YOUR_API_KEY"
require 'net/http'
require 'uri'
charge_id = 'ch_pQsQ4kz3Af6Mb9rCupnWj6VFzxJsmkYK'
uri = URI.parse("https://api-sandbox.paywithsoap.com/api/v1/charges/#{charge_id}/refund")
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Post.new(uri.request_uri)
request['Authorization'] = 'Bearer YOUR_API_KEY'
response = http.request(request)
puts response.body
const axios = require('axios');
const refundCharge = async (chargeId) => {
try {
const response = await axios.post(
`https://api-sandbox.paywithsoap.com/api/v1/charges/${chargeId}/refund`,
{},
{
headers: {
'Authorization': 'Bearer YOUR_API_KEY'
}
}
);
console.log(response.data);
} catch (error) {
console.error(error);
}
};
refundCharge('ch_pQsQ4kz3Af6Mb9rCupnWj6VFzxJsmkYK');
{
"error": "Refund failed",
"responsetext": "Refund amount may not exceed the transaction balance"
}
{
"error": "Unable to refund charge.",
"hint": "Please verify the charge_id is correct. \n charge_id: ch_pQsQ4kz3Af6Mb9rCupnWj6VFzxJsmkYK"
}
Authorizations
Bearer token authentication using your API key (key_...). Used for every endpoint except POST /api/v1/device_pings.
Path Parameters
Unique identifier of the charge to refund.
"ch_pQsQ4kz3Af6Mb9rCupnWj6VFzxJsmkYK"
Response
The refund succeeded. Returns the charge in the same shape as GET /api/v1/charges/{id}, with status "refunded".
"ch_pQsQ4kz3Af6Mb9rCupnWj6VFzxJsmkYK"
Charge amount in cents.
2999
Direction of the charge: credit for deposits (money into the customer balance), debit for withdrawals (money out).
credit, debit "debit"
"USD"
Current state of the charge. Not every value is reachable on every flow or processor: held occurs on withdrawals that place a hold; voided and returned follow a succeeded charge that the processor later reversed; cancelled is processor-specific — a transfer that was accepted (usually pending) and then cancelled by the rail, seen on bank-transfer deposits and payouts — and emits the opt-in checkout.cancelled event (ask Soap whether your rails emit it); refunded is set by a full refund of a succeeded NMI or Rapyd card deposit via POST /api/v1/charges/{id}/refund or the dashboard, and emits the opt-in checkout.refunded event. See the charge lifecycle for the full transition map and which webhook each transition emits.
created, pending, succeeded, failed, held, voided, returned, refunded, cancelled "succeeded"
Machine-readable failure code. Always present; null unless the charge failed (a cancelled charge may also carry one).
null
Human-readable failure reason. Always present; null unless the charge failed (a cancelled charge may also carry one).
null
"2026-05-31T10:30:00.000Z"
"2026-05-31T10:30:05.000Z"
Show child attributes
Show child attributes
Payment method used for the charge. Exactly one of card, bank_account, or crypto_wallet is populated, matching payment_type.
Show child attributes
Show child attributes
Address Verification System result from the processor. Card charges only.
null
CVV verification result from the processor. Card charges only.
null
Authorization code returned by the card network. Card charges only.
null
External processor's identifier for this charge. Card charges only.
null
3D Secure verification details. Card charges only: present as an object when 3DS was performed and as null otherwise. Absent on bank and crypto charges.
Show child attributes
Show child attributes

