Ir al contenido

API

En esta página

Crear

Crea una identidad de cobro reutilizable para un cliente, con tu propio identificador externo y una dirección permanente en cada red admitida.

POST/v1/payment-channels

Clave de API: Payment

Un canal de pago es una identidad de cobro permanente para un cliente tuyo. A diferencia de una factura, no tiene importe, ni plazo, ni estado final: lo creas una vez, enseñas sus direcciones y cada transferencia que llega se convierte en un depósito abonado a tu saldo.

Cuerpo de la petición

Parámetro Tipo Obligatorio Descripción
project_id string Identificador de proyecto con prefijo (prj_…). El proyecto decide qué tokens acepta el canal.
external_id string Tu propio identificador estable del cliente, de hasta 128 caracteres. Es inmutable una vez que el canal existe.
{
  "project_id": "prj_xFukZuAJZR06pLVBh3uwzv",
  "external_id": "customer-42"
}

Idempotencia

La pareja project_id + external_id, dentro del entorno de la clave con la que firmas, es la clave de idempotencia. No hay nada más que enviar, así que una llamada repetida no deja ningún conflicto que resolver.

  • Primera llamada → 201 Created, con una cabecera Location que apunta al canal nuevo.
  • Cualquier llamada posterior con la misma pareja → 200 OK con ese mismo canal. No se crea ningún duplicado.
  • El mismo external_id en Sandbox y en producción son dos canales independientes.

Envía el identificador de cliente que ya usas en tu sistema y llama a este endpoint en cada cobro, en vez de guardarte el id pc_. Repetir la llamada no es un error ni crea un duplicado: los dos códigos devuelven un canal, y el 200 trae las direcciones y los tokens que el canal tiene hoy. Reintentar una petición que se quedó sin respuesta te devuelve el canal original en lugar de abrir uno nuevo.

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"
BODY='{"project_id":"prj_xxxxxxxxxxxx","external_id":"customer-42"}'

TS=$(date +%s)
BODY_HASH=$(printf '%s' "$BODY" | openssl dgst -sha256 -hex | sed 's/^.* //')
SIGNATURE=$(printf '%s\n%s\n%s\n%s\n%s' "$TS" POST /v1/payment-channels '' "$BODY_HASH" \
  | openssl dgst -sha256 -hmac "$API_SECRET" -binary | base64)

curl -sS https://api.paymos.io/v1/payment-channels \
  -H "Authorization: HMAC-SHA256 $API_KEY_ID:$SIGNATURE" \
  -H "X-Request-Timestamp: $TS" \
  -H "Content-Type: application/json" \
  -d "$BODY"
import { Paymos } from '@paymos/sdk';

const paymos = new Paymos({
  apiKey: process.env.PAYMOS_API_KEY,
  apiSecret: process.env.PAYMOS_API_SECRET,
});

const channel = await paymos.paymentChannels.create({
  projectId: 'prj_xxxxxxxxxxxx',
  externalId: 'customer-42',
});

for (const rail of channel.networks) {
  console.log(rail.network, rail.address ?? rail.status);
}
import os

from paymos import Paymos

paymos = Paymos(
    api_key=os.environ["PAYMOS_API_KEY"],
    api_secret=os.environ["PAYMOS_API_SECRET"],
)
channel = paymos.payment_channels.create(
    project_id="prj_xxxxxxxxxxxx",
    external_id="customer-42",
)

for rail in channel["networks"]:
    print(rail["network"], rail.get("address", rail["status"]))
<?php
use Paymos\Client;
use Paymos\ClientConfig;

$paymos = new Client(new ClientConfig(
    getenv('PAYMOS_API_KEY'),
    getenv('PAYMOS_API_SECRET')
));
$channel = $paymos->paymentChannels()->create(array(
    'project_id' => 'prj_xxxxxxxxxxxx',
    'external_id' => 'customer-42',
));

foreach ($channel['networks'] as $rail) {
    $address = isset($rail['address']) ? $rail['address'] : $rail['status'];
    echo $rail['network'] . ' ' . $address . 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)
	}
	channel, err := client.PaymentChannels.Create(context.Background(), paymos.CreatePaymentChannelParams{
		ProjectID:  "prj_xxxxxxxxxxxx",
		ExternalID: "customer-42",
	})
	if err != nil {
		panic(err)
	}
	for _, rail := range channel.Networks {
		fmt.Println(rail.Network, addressOrStatus(rail))
	}
}

func addressOrStatus(rail paymos.PaymentChannelNetwork) string {
	if rail.Address == nil {
		return string(rail.Status)
	}
	return *rail.Address
}
using Paymos;

using var paymos = new PaymosClient(
    Environment.GetEnvironmentVariable("PAYMOS_API_KEY")!,
    Environment.GetEnvironmentVariable("PAYMOS_API_SECRET")!);

var channel = await paymos.PaymentChannels.CreateAsync(new CreatePaymentChannelRequest(
    ProjectId: "prj_xxxxxxxxxxxx",
    ExternalId: "customer-42"));

foreach (var rail in channel.Networks)
{
    Console.WriteLine($"{rail.Network} {rail.Address ?? rail.Status}");
}
import io.paymos.CreatePaymentChannelRequest;
import io.paymos.PaymentChannel;
import io.paymos.PaymentChannelNetwork;
import io.paymos.PaymosClient;

PaymosClient paymos = new PaymosClient(
    System.getenv("PAYMOS_API_KEY"),
    System.getenv("PAYMOS_API_SECRET"));

PaymentChannel channel = paymos.paymentChannels.create(
    new CreatePaymentChannelRequest("prj_xxxxxxxxxxxx", "customer-42"));

for (PaymentChannelNetwork rail : channel.networks()) {
    System.out.println(rail.network() + " "
        + (rail.address() == null ? rail.status() : rail.address()));
}
require 'paymos'

paymos = Paymos::Client.new(
  api_key: ENV.fetch('PAYMOS_API_KEY'),
  api_secret: ENV.fetch('PAYMOS_API_SECRET')
)
channel = paymos.payment_channels.create(
  project_id: 'prj_xxxxxxxxxxxx',
  external_id: 'customer-42'
)

channel.networks.each do |rail|
  puts "#{rail.network} #{rail.address || rail.status}"
end
use paymos::{CreatePaymentChannelRequest, PaymosClient};

let paymos = PaymosClient::new(
    std::env::var("PAYMOS_API_KEY")?,
    std::env::var("PAYMOS_API_SECRET")?,
)?;
let channel = paymos
    .payment_channels()
    .create(&CreatePaymentChannelRequest {
        project_id: "prj_xxxxxxxxxxxx".to_owned(),
        external_id: "customer-42".to_owned(),
    })
    .await?;

for rail in &channel.networks {
    println!("{} {}", rail.network, rail.address.as_deref().unwrap_or(&rail.status));
}

Respuesta (201 Created / 200 si se repite la llamada)

{
  "id": "pc_2QhKZv6mRt9WdA3nYpLbXf",
  "project_id": "prj_xFukZuAJZR06pLVBh3uwzv",
  "external_id": "customer-42",
  "status": "active",
  "is_accepting_payments": true,
  "is_fully_provisioned": false,
  "is_test": false,
  "applied_fee_percent": 1.0,
  "customer_fee_percent": 0,
  "networks": [
    {
      "network": "TRC20",
      "status": "active",
      "address": "TN3W4H6rK2ce4vX9YnFQHwKENnHjoxb3m9",
      "tokens": [
        { "symbol": "USDT", "minimum_deposit": "1.00" },
        { "symbol": "USDC", "minimum_deposit": "1.00" }
      ]
    },
    {
      "network": "ERC20",
      "status": "active",
      "address": "0x9f2a6c1b4e8d7350ab11c9e5f0d2743b86ce41d9",
      "tokens": [
        { "symbol": "USDT", "minimum_deposit": "12.00" },
        { "symbol": "USDC", "minimum_deposit": "12.00" }
      ]
    },
    {
      "network": "SOL",
      "status": "provisioning",
      "tokens": [
        { "symbol": "USDC", "minimum_deposit": null }
      ]
    }
  ],
  "created_at": 1767225600,
  "updated_at": 1767225780
}

Redes y direcciones

Un canal no tiene lista de tokens propia. Acepta lo que acepte el proyecto: activar un token ahí lo añade a todos los canales de ese proyecto y desactivarlo lo quita de todos a la vez. El conjunto aceptado se gestiona en Panel → Proyectos.

Cada entrada de networks describe una cadena:

Campo Significado
network Código de red, por ejemplo TRC20 o ERC20.
status active en cuanto la dirección existe; provisioning hasta ese momento.
address La dirección de depósito permanente. No viene hasta que esa red termina de aprovisionarse: es todavía no, no nunca. Una vez devuelta, ya no cambia.
tokens Lo que esa red acepta ahora mismo. Cada entrada es un objeto con symbol y minimum_deposit, no un símbolo suelto.

Dos reglas gobiernan lo que le enseñas a un cliente:

  • Presenta únicamente las redes active con un array tokens no vacío. Una entrada en provisioning todavía no tiene dirección que enseñar.
  • Deja de ofrecer una vía que la API ya no devuelve. Las direcciones nunca se reasignan, pero una red que quitaste del proyecto desaparece de la respuesta, y el dinero enviado ahí ya no se espera.

Una dirección, una vez devuelta, es de ese canal para siempre. Guárdala en caché: releer el canal devuelve el mismo valor, y esa misma dirección sirve para todos los tokens de esa red.

Cada entrada de token trae su propio minimum_deposit: la transferencia más pequeña que esa vía abona. Tómalo de la misma respuesta que estás mostrando y enséñalo junto a la dirección. Obtener canal explica el campo, incluido el caso null.

Aprovisionamiento

Un canal nuevo de producción nace en provisioning y sin direcciones. Cada red se aprovisiona por separado y queda utilizable en cuanto existe su propia dirección, sin esperar a la última cadena. El canal pasa a active con la primera dirección lista y ahí se queda.

is_fully_provisioned te dice si ya están listas todas las redes requeridas. Un canal puede estar active sin estar aprovisionado del todo: es el estado normal mientras las cadenas restantes se ponen al día.

Los canales de Sandbox están listos al instante. Sus direcciones se derivan en local y no tocan ninguna cadena.

Comisiones

applied_fee_percent y customer_fee_percent son tus tarifas actuales. Se muestran a título informativo y no son una cotización: cambian cuando cambia tu tarifa.

Los números que mandan viven en cada depósito. Todo depósito registra las tarifas vigentes en el instante en que se atribuyó e informa de su propio gross, fee y net. Un cambio de tarifa nunca reescribe un pago que ya recibiste. No existe ningún endpoint para previsualizar la comisión: crea un depósito en Sandbox si quieres ver la aritmética.

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_).
project_not_found 404 project_id no resuelve a nada visible para esta credencial.
payment_channel_external_id_invalid 400 external_id está vacío, en blanco o supera los 128 caracteres.
payment_channel_project_has_no_supported_tokens 409 El proyecto no activa ningún token que un canal pueda cobrar.

Consulta Códigos de error para el catálogo completo.

Lo que esta API no tiene

No hay endpoint de actualización, de borrado, de reasignación, de URL de callback, ni de tokens o previsualización de comisión por canal. La identidad de un canal es inmutable por diseño: una dirección que un cliente ya guardó no puede pasar a manos de otro. Para dar de baja un canal, bloquéalo.