Get payin status
curl --request POST \
--url https://api.pontisglobe.com/api/v1/gateway/payments/status \
--header 'Content-Type: application/json' \
--header 'x-api-key: <api-key>' \
--header 'x-signature: <x-signature>' \
--header 'x-timestamp: <x-timestamp>' \
--data '
{
"merchant_reference": "ORDER-1042"
}
'import requests
url = "https://api.pontisglobe.com/api/v1/gateway/payments/status"
payload = { "merchant_reference": "ORDER-1042" }
headers = {
"x-timestamp": "<x-timestamp>",
"x-signature": "<x-signature>",
"x-api-key": "<api-key>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {
'x-timestamp': '<x-timestamp>',
'x-signature': '<x-signature>',
'x-api-key': '<api-key>',
'Content-Type': 'application/json'
},
body: JSON.stringify({merchant_reference: 'ORDER-1042'})
};
fetch('https://api.pontisglobe.com/api/v1/gateway/payments/status', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.pontisglobe.com/api/v1/gateway/payments/status",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'merchant_reference' => 'ORDER-1042'
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json",
"x-api-key: <api-key>",
"x-signature: <x-signature>",
"x-timestamp: <x-timestamp>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.pontisglobe.com/api/v1/gateway/payments/status"
payload := strings.NewReader("{\n \"merchant_reference\": \"ORDER-1042\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("x-timestamp", "<x-timestamp>")
req.Header.Add("x-signature", "<x-signature>")
req.Header.Add("x-api-key", "<api-key>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.pontisglobe.com/api/v1/gateway/payments/status")
.header("x-timestamp", "<x-timestamp>")
.header("x-signature", "<x-signature>")
.header("x-api-key", "<api-key>")
.header("Content-Type", "application/json")
.body("{\n \"merchant_reference\": \"ORDER-1042\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.pontisglobe.com/api/v1/gateway/payments/status")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["x-timestamp"] = '<x-timestamp>'
request["x-signature"] = '<x-signature>'
request["x-api-key"] = '<api-key>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"merchant_reference\": \"ORDER-1042\"\n}"
response = http.request(request)
puts response.read_body{
"ok": true,
"data": {
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"merchant_reference": "ORDER-1042",
"status": "created",
"requested_amount": "100.00",
"requested_currency": "USDT",
"deposit_asset": "USDC",
"expires_at": "2023-11-07T05:31:56Z",
"created_at": "2023-11-07T05:31:56Z",
"surface": "pontis_hosted",
"deposit_chain": "trc20",
"received_amount": "99.80",
"received_currency": "USD",
"merchant_fee_amount": "0.50",
"credited_amount": "99.50",
"paid_at": "2023-11-07T05:31:56Z"
}
}{
"ok": false,
"request_id": "8c1f4b2a-9d3e-4a71-b6c8-5e2f0a7d1934",
"error": {
"code": "insufficient_funds",
"message": "Insufficient available balance. Available: 5.00 USDT"
}
}{
"ok": false,
"request_id": "8c1f4b2a-9d3e-4a71-b6c8-5e2f0a7d1934",
"error": {
"code": "insufficient_funds",
"message": "Insufficient available balance. Available: 5.00 USDT"
}
}{
"ok": false,
"request_id": "8c1f4b2a-9d3e-4a71-b6c8-5e2f0a7d1934",
"error": {
"code": "insufficient_funds",
"message": "Insufficient available balance. Available: 5.00 USDT"
}
}{
"ok": false,
"request_id": "8c1f4b2a-9d3e-4a71-b6c8-5e2f0a7d1934",
"error": {
"code": "insufficient_funds",
"message": "Insufficient available balance. Available: 5.00 USDT"
}
}{
"ok": false,
"request_id": "8c1f4b2a-9d3e-4a71-b6c8-5e2f0a7d1934",
"error": {
"code": "insufficient_funds",
"message": "Insufficient available balance. Available: 5.00 USDT"
}
}{
"ok": false,
"request_id": "8c1f4b2a-9d3e-4a71-b6c8-5e2f0a7d1934",
"error": {
"code": "insufficient_funds",
"message": "Insufficient available balance. Available: 5.00 USDT"
}
}Payins
/api/v1/gateway/payments/status
Read back one payin by your own order reference — status, amounts, and the currency you were credited in.
POST
/
api
/
v1
/
gateway
/
payments
/
status
Get payin status
curl --request POST \
--url https://api.pontisglobe.com/api/v1/gateway/payments/status \
--header 'Content-Type: application/json' \
--header 'x-api-key: <api-key>' \
--header 'x-signature: <x-signature>' \
--header 'x-timestamp: <x-timestamp>' \
--data '
{
"merchant_reference": "ORDER-1042"
}
'import requests
url = "https://api.pontisglobe.com/api/v1/gateway/payments/status"
payload = { "merchant_reference": "ORDER-1042" }
headers = {
"x-timestamp": "<x-timestamp>",
"x-signature": "<x-signature>",
"x-api-key": "<api-key>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {
'x-timestamp': '<x-timestamp>',
'x-signature': '<x-signature>',
'x-api-key': '<api-key>',
'Content-Type': 'application/json'
},
body: JSON.stringify({merchant_reference: 'ORDER-1042'})
};
fetch('https://api.pontisglobe.com/api/v1/gateway/payments/status', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.pontisglobe.com/api/v1/gateway/payments/status",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'merchant_reference' => 'ORDER-1042'
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json",
"x-api-key: <api-key>",
"x-signature: <x-signature>",
"x-timestamp: <x-timestamp>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.pontisglobe.com/api/v1/gateway/payments/status"
payload := strings.NewReader("{\n \"merchant_reference\": \"ORDER-1042\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("x-timestamp", "<x-timestamp>")
req.Header.Add("x-signature", "<x-signature>")
req.Header.Add("x-api-key", "<api-key>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.pontisglobe.com/api/v1/gateway/payments/status")
.header("x-timestamp", "<x-timestamp>")
.header("x-signature", "<x-signature>")
.header("x-api-key", "<api-key>")
.header("Content-Type", "application/json")
.body("{\n \"merchant_reference\": \"ORDER-1042\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.pontisglobe.com/api/v1/gateway/payments/status")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["x-timestamp"] = '<x-timestamp>'
request["x-signature"] = '<x-signature>'
request["x-api-key"] = '<api-key>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"merchant_reference\": \"ORDER-1042\"\n}"
response = http.request(request)
puts response.read_body{
"ok": true,
"data": {
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"merchant_reference": "ORDER-1042",
"status": "created",
"requested_amount": "100.00",
"requested_currency": "USDT",
"deposit_asset": "USDC",
"expires_at": "2023-11-07T05:31:56Z",
"created_at": "2023-11-07T05:31:56Z",
"surface": "pontis_hosted",
"deposit_chain": "trc20",
"received_amount": "99.80",
"received_currency": "USD",
"merchant_fee_amount": "0.50",
"credited_amount": "99.50",
"paid_at": "2023-11-07T05:31:56Z"
}
}{
"ok": false,
"request_id": "8c1f4b2a-9d3e-4a71-b6c8-5e2f0a7d1934",
"error": {
"code": "insufficient_funds",
"message": "Insufficient available balance. Available: 5.00 USDT"
}
}{
"ok": false,
"request_id": "8c1f4b2a-9d3e-4a71-b6c8-5e2f0a7d1934",
"error": {
"code": "insufficient_funds",
"message": "Insufficient available balance. Available: 5.00 USDT"
}
}{
"ok": false,
"request_id": "8c1f4b2a-9d3e-4a71-b6c8-5e2f0a7d1934",
"error": {
"code": "insufficient_funds",
"message": "Insufficient available balance. Available: 5.00 USDT"
}
}{
"ok": false,
"request_id": "8c1f4b2a-9d3e-4a71-b6c8-5e2f0a7d1934",
"error": {
"code": "insufficient_funds",
"message": "Insufficient available balance. Available: 5.00 USDT"
}
}{
"ok": false,
"request_id": "8c1f4b2a-9d3e-4a71-b6c8-5e2f0a7d1934",
"error": {
"code": "insufficient_funds",
"message": "Insufficient available balance. Available: 5.00 USDT"
}
}{
"ok": false,
"request_id": "8c1f4b2a-9d3e-4a71-b6c8-5e2f0a7d1934",
"error": {
"code": "insufficient_funds",
"message": "Insufficient available balance. Available: 5.00 USDT"
}
}Returns the current state of a payment you created. Scoped to your own account: a reference
belonging to another merchant is indistinguishable from one that does not exist — both return
See Errors for the envelope shape shared by every endpoint.
not_found.
This is for reconciliation and support, not for driving fulfilment. Fulfil on the
callback; polling this endpoint in a loop will be rate limited and will always
be slower than the callback already on its way to you.
The field above is what you encrypt, not what goes on the wire. The body is always
{ "data": "<aes-256-gcm ciphertext>" }, and your IP must be on the account allowlist — both
covered in Authentication.No JWT — same as create. Payin endpoints are authenticated by
your API key, request signature and IP allowlist. A payin-only integration never calls
/api/v1/user/login.You look a payment up by your own reference, not by our
id. There is nothing extra to store:
the order id you already have is the key.Example request
All examples assume you have already encrypted the body and signed the request. The transport is
identical to payouts, so the helper in Payouts
quickstart is the same one both products use.
const res = await call('/api/v1/gateway/payments/status', {
merchant_reference: 'ORDER-1042',
})
const payment = res.data
// Paid in full — safe to fulfil. Amounts are decimal strings, so compare with
// a decimal library, never by parsing to a float.
if (payment.status === 'paid' || payment.status === 'overpaid') {
fulfil(payment.merchant_reference)
} else if (payment.status === 'underpaid') {
// Money arrived, but LESS than the order. Never fulfil on this alone.
holdForReview(payment.merchant_reference)
}
status, body = call('/api/v1/gateway/payments/status', {
'merchant_reference': 'ORDER-1042',
})
payment = body['data']
# Paid in full — safe to fulfil. Amounts are decimal strings; use Decimal,
# never float.
if payment['status'] in ('paid', 'overpaid'):
fulfil(payment['merchant_reference'])
elif payment['status'] == 'underpaid':
# Money arrived, but LESS than the order. Never fulfil on this alone.
hold_for_review(payment['merchant_reference'])
curl -X POST "https://api.pontisglobe.com/api/v1/gateway/payments/status" \
-H "content-type: application/json" \
-H "x-api-key: YOUR_API_KEY" \
-H "x-timestamp: 1748023400" \
-H "x-signature: 2f8a9b…" \
-d '{"data":"<encrypted blob>"}'
req, _ := http.NewRequest("POST",
"https://api.pontisglobe.com/api/v1/gateway/payments/status",
strings.NewReader(`{"data":"`+encryptedBody+`"}`))
req.Header.Set("content-type", "application/json")
req.Header.Set("x-api-key", apiKey)
req.Header.Set("x-timestamp", timestamp)
req.Header.Set("x-signature", signature)
res, _ := http.DefaultClient.Do(req)
HttpRequest req = HttpRequest.newBuilder()
.uri(URI.create("https://api.pontisglobe.com/api/v1/gateway/payments/status"))
.header("content-type", "application/json")
.header("x-api-key", apiKey)
.header("x-timestamp", timestamp)
.header("x-signature", signature)
.POST(HttpRequest.BodyPublishers.ofString("{\"data\":\"" + encryptedBody + "\"}"))
.build();
HttpResponse<String> res = HttpClient.newHttpClient().send(req, BodyHandlers.ofString());
val req = Request.Builder()
.url("https://api.pontisglobe.com/api/v1/gateway/payments/status")
.post("""{"data":"$encryptedBody"}""".toRequestBody("application/json".toMediaType()))
.addHeader("x-api-key", apiKey)
.addHeader("x-timestamp", timestamp)
.addHeader("x-signature", signature)
.build()
val res = OkHttpClient().newCall(req).execute()
$ch = curl_init('https://api.pontisglobe.com/api/v1/gateway/payments/status');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
'content-type: application/json',
"x-api-key: $apiKey",
"x-timestamp: $timestamp",
"x-signature: $signature",
],
CURLOPT_POSTFIELDS => json_encode(['data' => $encryptedBody]),
CURLOPT_RETURNTRANSFER => true,
]);
$res = curl_exec($ch);
uri = URI('https://api.pontisglobe.com/api/v1/gateway/payments/status')
req = Net::HTTP::Post.new(uri, {
'content-type' => 'application/json',
'x-api-key' => api_key,
'x-timestamp' => timestamp,
'x-signature' => signature,
})
req.body = { data: encrypted_body }.to_json
res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) }
var req = new HttpRequestMessage(HttpMethod.Post,
"https://api.pontisglobe.com/api/v1/gateway/payments/status");
req.Headers.Add("x-api-key", apiKey);
req.Headers.Add("x-timestamp", timestamp);
req.Headers.Add("x-signature", signature);
req.Content = new StringContent($"{{\"data\":\"{encryptedBody}\"}}",
Encoding.UTF8, "application/json");
var res = await new HttpClient().SendAsync(req);
var req = URLRequest(url: URL(string: "https://api.pontisglobe.com/api/v1/gateway/payments/status")!)
req.httpMethod = "POST"
req.setValue("application/json", forHTTPHeaderField: "content-type")
req.setValue(apiKey, forHTTPHeaderField: "x-api-key")
req.setValue(timestamp, forHTTPHeaderField: "x-timestamp")
req.setValue(signature, forHTTPHeaderField: "x-signature")
req.httpBody = #"{"data":"\#(encryptedBody)"}"#.data(using: .utf8)
let (data, _) = try await URLSession.shared.data(for: req)
Reading the response
All amounts are decimal strings, never numbers —"100.00", not 100.00. Parsing them into a
floating-point type will eventually cost you precision on a real payment.
Three different currencies appear in this one response.
requested_currency is what you
priced in, deposit_asset is what you were credited in, and received_currency belongs to our
internal settlement figure. Reconcile against credited_amount and deposit_asset together —
see the three currencies.Status values
status | Money moved? | Meaning | What to do |
|---|---|---|---|
created | no | Link issued, customer has not chosen how to pay yet | Wait |
awaiting_payment | no | Method chosen, waiting for funds on-chain | Wait |
paid | yes | Settled for the expected amount | Fulfil the order |
underpaid | yes | Customer paid less than the order. Credited anyway | Do not auto-fulfil — your policy |
overpaid | yes | Settled for more than expected. Credited in full | Fulfil; you owe the difference |
expired | no | The window passed with no payment | Not terminal — see below |
failed | no | The payment could not be completed | Treat as unpaid |
All three of
paid, underpaid and overpaid moved your balance — but only paid and
overpaid mean the customer paid in full. There is no minimum on an underpayment: 10 USDT
against a 100 USDT order settles as underpaid. Compare credited_amount against
requested_amount before fulfilling anything.expired means we stopped waiting, not no money arrived. The deposit address stays live, and
a late payment still settles, still credits you, and still fires a callback. A payment can move
from expired to paid, so do not make it terminal in your own system.Which fields are populated when
Money fields arenull until the payment settles. This is the single most common surprise here:
| Field | created | awaiting_payment | Settled |
|---|---|---|---|
id, merchant_reference, status, requested_* | ✅ | ✅ | ✅ |
expires_at, created_at | ✅ | ✅ | ✅ |
deposit_asset | default | ✅ | ✅ |
surface, deposit_chain | null | ✅ | ✅ |
received_*, merchant_fee_amount, credited_amount | null | null | ✅ |
paid_at | null | null | ✅ |
deposit_asset is never null — before your customer picks a method it holds the default
(USDT) rather than a choice they have made. Only read it as “the currency you were credited in”
once status is settled or deposit_chain is non-null.Telling the two 400s apart
validation_error means merchant_reference was missing, empty, or over 128 characters — the
payload reached us and failed validation.
bad_request means the envelope was wrong, so nothing was ever validated: a missing or skewed
x-timestamp, a body that is not {"data":"…"}, or a payload we could not decrypt with your
encryption secret. If you are seeing this on every call, the problem is in your signing helper, not
in your fields.
A 403 is either an IP that is not on your allowlist or the payin product not being enabled on the
account. A 429 means you are over 600 lookups per minute.
A reference that belongs to a different merchant returns
not_found too. The endpoint never
confirms that someone else’s order exists.Polling, if you must
The callback is the intended path and it is retried up to 8 times — but if you are recovering from a missed callback or reconciling a batch:- Look up on demand (a support query, an order older than expected), not on a fixed loop over every open order
- Stop at the first settled status:
paid,underpaidoroverpaidare final for fulfilment - Keep polling
expiredorders only briefly. A late payment fires a callback anyway, and we keep re-checking lapsed payments on our side
If a callback never arrived and this endpoint reports a settled status, the money is in your
balance — the delivery failed, not the payment. Check your callback URL is reachable and returning
2xx: see retries.
Authorizations
Identifies your account. Issued from Developer Tools in the dashboard.
Headers
Unix epoch in seconds — not milliseconds. Must be within ±5 minutes of our clock, which is what makes a captured request unusable later. Keep your client's clock NTP-synced.
Example:
1748023400
HMAC-SHA256 over the signing string, hex encoded. The timestamp is part of what is signed, so a replayed body cannot be re-dated. See https://docs.pontisglobe.com/authentication for how it is built.
Example:
"2f8a9b4c1d7e0a3f6b8c2d5e9f1a4b7c0d3e6f9a2b5c8d1e4f7a0b3c6d9e2f5a"
Body
application/json
Required string length:
1 - 128Example:
"ORDER-1042"