Zum Inhalt springen

API

Auf dieser Seite

Feed abfragen

Bestätigte Kanaleinzahlungen in Veröffentlichungsreihenfolge abfragen, mit stets vorrückendem Cursor: keine abgeschlossene Zahlung geht verloren.

GET/v1/payment-channel-deposits

API-Schlüssel: Payment

Ein Feed für die Anbindung, keine Verlaufsseite. Er gibt ausschließlich bestätigte Einzahlungen zurück, älteste zuerst, in einer globalen Veröffentlichungsreihenfolge, und er liefert immer einen Cursor mit. Fragen Sie ihn regelmäßig ab, dann sehen Sie jede abgeschlossene Zahlung genau einmal, in einer Reihenfolge, die sich unter Ihnen nie ändert.

Webhooks sind der schnelle Weg, dieser Feed der verlässliche. Betreiben Sie beide: Webhooks liefern Sekunden, der Feed garantiert, dass Sie am Ende alles sehen — auch dann, wenn Ihr Endpunkt ausgefallen war.

Query-Parameter

Parameter Typ Standard Beschreibung
limit Ganzzahl 20 Seitengröße von 1 bis 100.
cursor Zeichenkette Undurchsichtiger Cursor aus dem next_cursor der vorherigen Seite.
project_id Zeichenkette Auf ein Projekt einschränken (prj_…).
payment_channel_id Zeichenkette Auf einen Kanal einschränken (pc_…).
confirmed_from Unix-Sekunden Nur Einzahlungen, die zu diesem Zeitpunkt oder später bestätigt wurden. Begrenzt allein die erste Abfrage — siehe unten.

Einen Parameter status gibt es bewusst nicht. Wer confirming abfragen könnte, würde Geld als abgeschlossen behandeln, das eine Reorganisation noch wegnehmen kann.

Beispiel

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

Codebeispiele

Jeder Reiter nutzt das offizielle SDK der jeweiligen Sprache. Signatur, Serialisierung, sichere Wiederholungen und typisierte Fehler übernimmt das 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);

Antwort (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…"
}

So fragen Sie richtig ab

  • 01Beginnen Sie ohne Cursor. Verarbeiten Sie die gesamte Seite.
  • 02Speichern Sie next_cursor, nachdem die Seite vollständig verarbeitet ist, nicht davor.
  • 03Senden Sie ihn bei der nächsten Abfrage zurück. Und so weiter.

Der Vertrag hinter diesen drei Schritten:

  • next_cursor ist nie null. Er steht in jeder Antwort, auch auf einer leeren Seite. Die Positionen sind global und nicht je Händler vergeben, deshalb muss auch ein Filter, der nichts trifft, über die übersprungenen Positionen hinausrücken — und deshalb durchsucht eine ruhende Anbindung nicht endlos denselben Bereich.
  • Halten Sie bei einer leeren oder kurzen Seite an, nicht bei einem null-Cursor. Rechnungen auflisten endet, sobald next_cursor auf null steht; dieser Feed endet nie so, und eine Schleife mit dieser Bedingung wird nie verlassen. Weniger Einträge als limit heißt: Für Sie liegt gerade nichts mehr an. Speichern Sie den Cursor, verlassen Sie die Schleife und fragen Sie nach Ihrem Zeitplan erneut ab.
  • confirmed_from begrenzt allein die erste Abfrage. Ab der zweiten entscheidet der gespeicherte Cursor, wo Sie fortsetzen. Senden Sie denselben Wert unverändert neben dem Cursor mit: Er gehört zu dem, woran der Cursor gebunden ist, und wird er weggelassen oder nach vorn geschoben, ist der Cursor ungültig.
  • Die Reihenfolge ist monoton. Eine Einzahlung wird einmal an einer festen Position veröffentlicht, und eine spätere Bestätigung kann nie vor einer früheren auftauchen, die Sie bereits gelesen haben.
  • Das Vorrücken ist Ihre Entscheidung. Legen Sie einen älteren Cursor erneut vor, sehen Sie Einzahlungen unter Umständen ein zweites Mal. Das ist unbedenklich, solange Sie über id deduplizieren — die pcd_-Kennung bleibt über Webhook-Zustellung, Feed und jede Wiederholung hinweg dieselbe.
  • Cursor laufen nach 24 Stunden ab — planen Sie den Abruf also mindestens einmal täglich ein. Ein Cursor ist die Stelle, an der Sie weiterlesen, kein Lesezeichen für nächste Woche: Wer länger schweigt, bekommt 400 pagination_cursor_invalid und beginnt neu, begrenzt durch confirmed_from.
  • Ein Cursor ist außerdem gebunden an Ihre Zugangsdaten, deren Umgebung, deren Projektumfang und die gesendeten Filter. Ändern Sie einen Filter oder erweitern Sie den Projektzugriff des Schlüssels, wird der alte Cursor ebenso abgewiesen. Beginnen Sie dann bei der ersten Seite: Die Deduplizierung über pcd_ macht eine vollständige Neusynchronisierung harmlos.

Wenn der Feed stehen bleibt

Lässt sich eine einzelne Einzahlung nicht darstellen, wird sie weder übersprungen noch bringt sie die Seite zu Fall. Der Feed liefert alles davor, hält next_cursor an dieser Position und nennt Ihnen, welche Einzahlung betroffen ist und warum:

{
  "items": [],
  "next_cursor": "CfDJ8JvN…",
  "blocked": {
    "deposit_id": "pcd_4KdQzT9rVn6WsB1mYhFxLg",
    "reason": "booked_auth_missing"
  }
}

blocked fehlt auf jeder normalen Seite. Steht es dort:

Grund Bedeutung
booked_auth_missing Die Zahlung ist bestätigt, ihre Buchung ist aber noch nicht geschrieben.
inconsistent Die Einzahlung, ihr Kanal und ihr Datensatz aus der Chain widersprechen einander.
deposit_missing Der veröffentlichte Datensatz führt zu keiner Einzahlung mehr.
channel_missing Der Kanal der Einzahlung ließ sich nicht laden.
transfer_missing Der Einzahlung fehlt der verknüpfte Datensatz aus der Chain, aus dem sie ihre Belege zieht.
not_confirmed Eine veröffentlichte Einzahlung steht nicht auf bestätigt; in diesen Feed kommt nur endgültiges Geld.

Fragen Sie weiter ab. next_cursor bleibt bewusst unterhalb der blockierten Position, der Feed läuft also von selbst weiter, sobald der zugrunde liegende Datensatz repariert ist, und dazwischen geht nichts verloren. Jeder dieser Fälle ist eine Betriebsstörung auf Plattformseite — wenden Sie sich mit der deposit_id an den Support, wenn er bestehen bleibt.

Fehler

Code HTTP Wann
payment_channels_disabled 503 Zahlungskanäle sind für Ihr Konto in dieser Umgebung nicht freigeschaltet.
payment_key_required 403 Die Anfrage wurde mit einem Payout-Schlüssel (rk_) signiert.
pagination_cursor_invalid 400 Der Cursor ist fehlerhaft, abgelaufen oder an andere Filter oder einen anderen Geltungsbereich gebunden.
field_invalid_format 400 project_id oder payment_channel_id ist keine gültige Kennung mit Präfix.
field_out_of_range 400 limit liegt außerhalb von 1–100.
query_parameter_unknown 400 Ein Parameter, der nicht in der Tabelle oben steht.

Den vollständigen Katalog finden Sie unter Fehlercodes.