En esta página
Sondear depósitos
Sondea los depósitos confirmados de tus canales en orden de publicación, con un cursor que siempre avanza y no se salta ni repite ningún pago liquidado.
Clave de API: Payment
Es un flujo de integración (feed), no una página de historial. Devuelve solo depósitos confirmados, de más antiguo a más nuevo, en un orden de publicación global, y siempre te entrega un cursor. Sondéalo a intervalos regulares y verás cada pago liquidado exactamente una vez, en un orden que no se te mueve por debajo.
Los webhooks son la vía rápida; este flujo es la vía fiable. Usa las dos: el webhook te da segundos, el flujo te garantiza que acabarás viéndolo todo aunque tu endpoint estuviera caído.
Parámetros de consulta
| Parámetro | Tipo | Por defecto | Descripción |
|---|---|---|---|
limit |
integer | 20 |
Tamaño de página, de 1 a 100. |
cursor |
string | — | Cursor opaco: el next_cursor de la página anterior. |
project_id |
string | — | Limita a un solo proyecto (prj_…). |
payment_channel_id |
string | — | Limita a un solo canal (pc_…). |
confirmed_from |
integer | — | Segundos Unix; solo depósitos confirmados en ese instante o después. Acota únicamente el primer sondeo — mira más abajo. |
No hay parámetro status, y es deliberado. Un sondeo capaz de pedir confirming estaría tratando como liquidado un dinero que una reorganización todavía puede llevarse.
Ejemplo
GET /v1/payment-channel-deposits?limit=100&cursor=CfDJ8JvN… HTTP/1.1
Host: api.paymos.io
Authorization: HMAC-SHA256 pk_live_…:…
X-Request-Timestamp: 1767225660
Ejemplos de código
Cada pestaña usa el SDK oficial de ese lenguaje. La firma, la serialización, los reintentos seguros y los errores tipados los resuelve el SDK.
API_KEY_ID="pk_live_xxxxxxxxxxxx"
API_SECRET="sk_live_xxxxxxxxxxxx"
QUERY="?limit=100"
TS=$(date +%s)
# No body: the hash slot is the EMPTY STRING, not sha256("") — its fixed
# e3b0c442… digest signs a different payload and the request is rejected. The
# query keeps its leading "?" exactly as transmitted.
SIGNATURE=$(printf '%s\n%s\n%s\n%s\n%s' "$TS" GET /v1/payment-channel-deposits "$QUERY" '' \
| openssl dgst -sha256 -hmac "$API_SECRET" -binary | base64)
curl -sS "https://api.paymos.io/v1/payment-channel-deposits$QUERY" \
-H "Authorization: HMAC-SHA256 $API_KEY_ID:$SIGNATURE" \
-H "X-Request-Timestamp: $TS"
import { Paymos } from '@paymos/sdk';
const paymos = new Paymos({
apiKey: process.env.PAYMOS_API_KEY,
apiSecret: process.env.PAYMOS_API_SECRET,
});
const page = await paymos.paymentChannelDeposits.read({ limit: 100 });
for (const deposit of page.items) {
console.log(deposit.id, deposit.net, deposit.currency);
}
console.log('resume from', page.nextCursor);
import os
from paymos import Paymos
paymos = Paymos(
api_key=os.environ["PAYMOS_API_KEY"],
api_secret=os.environ["PAYMOS_API_SECRET"],
)
page = paymos.payment_channel_deposits.read(limit=100)
for deposit in page["items"]:
print(deposit["id"], deposit["net"], deposit["currency"])
print("resume from", page["next_cursor"])
<?php
use Paymos\Client;
use Paymos\ClientConfig;
$paymos = new Client(new ClientConfig(
getenv('PAYMOS_API_KEY'),
getenv('PAYMOS_API_SECRET')
));
$page = $paymos->paymentChannelDeposits()->readPage(array('limit' => 100));
foreach ($page['items'] as $deposit) {
echo $deposit['id'] . ' ' . $deposit['net'] . ' ' . $deposit['currency'] . PHP_EOL;
}
echo 'resume from ' . $page['next_cursor'] . PHP_EOL;
package main
import (
"context"
"fmt"
"os"
paymos "github.com/Paymos-labs/go-sdk/v2"
)
func main() {
client, err := paymos.NewClient(os.Getenv("PAYMOS_API_KEY"), os.Getenv("PAYMOS_API_SECRET"))
if err != nil {
panic(err)
}
page, err := client.PaymentChannelDeposits.Read(context.Background(), paymos.PaymentChannelDepositFeedParams{
Limit: 100,
})
if err != nil {
panic(err)
}
for _, deposit := range page.Items {
fmt.Println(deposit.ID, deposit.Net, deposit.Currency)
}
fmt.Println("resume from", page.NextCursor)
}
using Paymos;
using var paymos = new PaymosClient(
Environment.GetEnvironmentVariable("PAYMOS_API_KEY")!,
Environment.GetEnvironmentVariable("PAYMOS_API_SECRET")!);
var page = await paymos.PaymentChannelDeposits.ReadAsync(
new PaymentChannelDepositFeedOptions(Limit: 100));
foreach (var deposit in page.Items)
{
Console.WriteLine($"{deposit.Id} {deposit.Net} {deposit.Currency}");
}
Console.WriteLine($"resume from {page.NextCursor}");
import io.paymos.PaymentChannelDeposit;
import io.paymos.PaymentChannelDepositFeedOptions;
import io.paymos.PaymentChannelDepositFeedPage;
import io.paymos.PaymosClient;
PaymosClient paymos = new PaymosClient(
System.getenv("PAYMOS_API_KEY"),
System.getenv("PAYMOS_API_SECRET"));
PaymentChannelDepositFeedPage page = paymos.paymentChannelDeposits.read(
PaymentChannelDepositFeedOptions.builder().limit(100).build());
for (PaymentChannelDeposit deposit : page.items()) {
System.out.println(deposit.id() + " " + deposit.net() + " " + deposit.currency());
}
System.out.println("resume from " + page.nextCursor());
require 'paymos'
paymos = Paymos::Client.new(
api_key: ENV.fetch('PAYMOS_API_KEY'),
api_secret: ENV.fetch('PAYMOS_API_SECRET')
)
page = paymos.payment_channel_deposits.read(limit: 100)
page.items.each do |deposit|
puts "#{deposit.id} #{deposit.net} #{deposit.currency}"
end
puts "resume from #{page.next_cursor}"
use paymos::{PaymentChannelDepositFeedParams, PaymosClient};
let paymos = PaymosClient::new(
std::env::var("PAYMOS_API_KEY")?,
std::env::var("PAYMOS_API_SECRET")?,
)?;
let page = paymos
.payment_channel_deposits()
.read(&PaymentChannelDepositFeedParams {
limit: Some(100),
..Default::default()
})
.await?;
for deposit in &page.items {
println!("{} {} {}", deposit.id, deposit.net, deposit.currency);
}
println!("resume from {}", page.next_cursor);
Respuesta (200 OK)
{
"items": [
{
"id": "pcd_8ScRvL4jNq2XkB7mTfZdWu",
"payment_channel_id": "pc_2QhKZv6mRt9WdA3nYpLbXf",
"project_id": "prj_xFukZuAJZR06pLVBh3uwzv",
"payment_channel_external_id": "customer-42",
"status": "confirmed",
"is_final": true,
"is_test": false,
"currency": "USDT",
"network": "TRC20",
"chain_id": 728126428,
"contract_address": "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t",
"gross": "100",
"fee": "1",
"net": "99",
"applied_fee_percent": 1.0,
"customer_fee_percent": 0,
"tx_hash": "9f4c1b7e30ad52c8ef6a1d84b0c39f27a5e6d138f4b90c72ae51d63b8407fc25",
"transfer_id": "9f4c1b7e30ad52c8ef6a1d84b0c39f27a5e6d138f4b90c72ae51d63b8407fc25:41a614f803b6fd780986a42c78ec9c7f77e6ded13c:41c9f6a2b7d0138e54ca3b91f6072ed48a5c1e93b7:0",
"source_address": "TW9s4RkAqBnLpVdX2ChYzUeGm7QfKt3NbZ",
"destination_address": "TN3W4H6rK2ce4vX9YnFQHwKENnHjoxb3m9",
"block_height": 68421905,
"first_included_block_timestamp": 1767225900,
"explorer_url": "https://tronscan.org/#/transaction/9f4c1b7e30ad52c8ef6a1d84b0c39f27a5e6d138f4b90c72ae51d63b8407fc25",
"created_at": 1767225870,
"updated_at": 1767225930,
"confirmed_at": 1767225930
},
{
"id": "pcd_5NmXqW7bHt3ZjR9kCvPyAe",
"payment_channel_id": "pc_2QhKZv6mRt9WdA3nYpLbXf",
"project_id": "prj_xFukZuAJZR06pLVBh3uwzv",
"payment_channel_external_id": "customer-42",
"status": "confirmed",
"is_final": true,
"is_test": false,
"currency": "USDC",
"network": "ERC20",
"chain_id": 1,
"contract_address": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
"gross": "42.5",
"fee": "0.425",
"net": "42.075",
"applied_fee_percent": 1.0,
"customer_fee_percent": 0,
"tx_hash": "0x1d7b64e2c05af398b2e714cd9a06f3518cb7e2409df6a1c8305b47ed12f9ca63",
"transfer_id": "0x1d7b64e2c05af398b2e714cd9a06f3518cb7e2409df6a1c8305b47ed12f9ca63:0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48:0x9f2a6c1b4e8d7350ab11c9e5f0d2743b86ce41d9:0",
"source_address": "0x4b8e1f60d3a97c25be04f7183a5cd9027ef6b14a",
"destination_address": "0x9f2a6c1b4e8d7350ab11c9e5f0d2743b86ce41d9",
"block_height": 22984117,
"first_included_block_timestamp": 1767226020,
"explorer_url": "https://etherscan.io/tx/0x1d7b64e2c05af398b2e714cd9a06f3518cb7e2409df6a1c8305b47ed12f9ca63",
"created_at": 1767226020,
"updated_at": 1767226260,
"confirmed_at": 1767226260
}
],
"next_cursor": "CfDJ8JvN…"
}
Cómo sondearlo bien
- 01Empieza sin cursor. Procesa la página entera.
- 02Guarda
next_cursordespués de haber procesado la página del todo, no antes. - 03Devuélvelo en el siguiente sondeo. Y vuelta a empezar.
El contrato que sostiene esos tres pasos:
- —
next_cursornunca es null. Viene en todas las respuestas, incluso en una página vacía. Las posiciones son globales, no de cada comercio, así que un filtro que no casa con nada tiene que avanzar igual por las que se salta: por eso una integración parada no se queda releyendo el mismo tramo para siempre. - —Párate en una página vacía o corta, no en un cursor
null. Listar facturas termina cuandonext_cursorpasa anull; este flujo no termina nunca así, y un bucle escrito con esa condición no sale jamás. Menos elementos quelimitsignifica que ahora mismo no queda nada para ti: guarda el cursor, sal del bucle y vuelve a sondear según tu calendario. - —
confirmed_fromacota únicamente el primer sondeo. A partir del segundo, quien decide dónde retomas es el cursor guardado. Sigue enviando el mismo valor sin tocarlo junto al cursor: forma parte de aquello a lo que el cursor está ligado, así que quitarlo o moverlo hacia delante lo invalida. - —El orden solo avanza. Un depósito se publica una vez, en una posición fija, y una confirmación posterior nunca aparece por delante de otra anterior que ya leíste.
- —Avanzar lo decides tú. Si reintentas con un cursor viejo puedes volver a ver depósitos. No pasa nada mientras descartes duplicados por
id: el identificadorpcd_es el mismo en la entrega por webhook, en la entrega por este flujo y en cualquier repetición. - —Los cursores caducan a las 24 horas, así que programa la consulta al menos una vez al día. El cursor es el punto por donde sigues, no un marcador que puedas guardar una semana: si el proceso calla más tiempo, vuelve con
400 pagination_cursor_invalidy toca empezar de nuevo acotando porconfirmed_from. - —El cursor también está ligado a tu credencial, a su entorno, a su alcance de proyectos y a los filtros que enviaste. Cambia un filtro, o amplía el acceso a proyectos de la clave, y el cursor antiguo se rechaza igual. Vuelve a empezar por la primera página: descartar duplicados por
pcd_deja una resincronización completa en algo inofensivo.
Cuándo se detiene el flujo
Si un depósito concreto no se puede representar, el flujo ni lo salta ni tumba la página. Entrega todo lo anterior, deja next_cursor clavado en esa posición y te dice de qué depósito se trata y por qué:
{
"items": [],
"next_cursor": "CfDJ8JvN…",
"blocked": {
"deposit_id": "pcd_4KdQzT9rVn6WsB1mYhFxLg",
"reason": "booked_auth_missing"
}
}
blocked no aparece en ninguna página normal. Cuando aparece:
| Motivo | Significado |
|---|---|
booked_auth_missing |
El pago está confirmado, pero su asiento contable todavía no se ha escrito. |
inconsistent |
El depósito, su canal y su registro en cadena no concuerdan entre sí. |
deposit_missing |
El registro publicado ya no resuelve a ningún depósito. |
channel_missing |
No se pudo cargar el canal del depósito. |
transfer_missing |
El depósito no tiene un registro en cadena enlazado del que sacar sus pruebas. |
not_confirmed |
Un depósito publicado no está en un estado confirmado; a este flujo solo entra dinero definitivo. |
Sigue sondeando. next_cursor se queda a propósito por debajo de la posición bloqueada, así que el flujo se reanuda solo en cuanto el registro de fondo queda reparado, y por el camino no se pierde nada. Todos estos casos son un fallo operativo de nuestro lado: escribe al soporte con el deposit_id si persiste.
Errores
| Code | HTTP | Cuándo |
|---|---|---|
payment_channels_disabled |
503 | Los canales de pago no están activados para tu cuenta en este entorno. |
payment_key_required |
403 | La petición se firmó con una clave Payout (rk_). |
pagination_cursor_invalid |
400 | El cursor está mal formado, ha caducado o está ligado a otros filtros o a otro alcance. |
field_invalid_format |
400 | project_id o payment_channel_id no es un identificador con prefijo válido. |
field_out_of_range |
400 | limit queda fuera de 1–100. |
query_parameter_unknown |
400 | Un parámetro que no está en la tabla de arriba. |
Consulta Códigos de error para el catálogo completo.