Ir al contenido

API

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.

GET/v1/payment-channel-deposits

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_cursor despué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_cursor nunca 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 cuando next_cursor pasa a null; este flujo no termina nunca así, y un bucle escrito con esa condición no sale jamás. Menos elementos que limit significa que ahora mismo no queda nada para ti: guarda el cursor, sal del bucle y vuelve a sondear según tu calendario.
  • confirmed_from acota ú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 identificador pcd_ 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_invalid y toca empezar de nuevo acotando por confirmed_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.