На странице
Аутентификация
Подписывайте каждый запрос Merchant API по HMAC-SHA256: обязательные заголовки, каноническая строка, метка времени и защита от повторов.
Каждый запрос к Paymos API аутентифицируется HMAC-SHA256-подписью канонической строки. Ключи создаются в дашборде — типы ключей, права и жизненный цикл описаны на странице API-ключи.
Схема подписи
Каждый API-запрос должен включать два заголовка — плюс один необязательный:
| Заголовок | Описание |
|---|---|
Authorization |
HMAC-SHA256 {apiKeyId}:{base64signature} — ID вашего API-ключа (напр. pk_live_...) и подпись |
X-Request-Timestamp |
Текущая Unix-метка (секунды) |
X-Correlation-Id |
(опционально) Ваш идентификатор корреляции для трассировки запросов |
Строка для подписи
Подпись — это Base64(HMAC-SHA256(api_secret, string_to_sign)), где:
string_to_sign = timestamp + "\n" + METHOD + "\n" + path + "\n" + query + "\n" + bodyHash
| Компонент | Описание |
|---|---|
timestamp |
То же значение, что и в X-Request-Timestamp |
METHOD |
HTTP-метод в верхнем регистре (POST, GET) |
path |
URL-декодированный путь, без query string (/v1/invoices) |
query |
Query string включая ? (пустая строка, если нет) |
bodyHash |
SHA-256 тела запроса в hex, строчными буквами. Пустая строка, если тела нет — пустую строку не хешировать |
Если тела нет, bodyHash — пустая строка. Не хешируйте пустую строку: SHA-256("") возвращает фиксированный непустой дайджест, и тогда сервер соберёт string_to_sign иначе, чем вы, — подпись не пройдёт проверку.
Построение подписи
Пример: POST /v1/invoices с JSON-телом
1709000000\nPOST\n/v1/invoices\n\n<sha256hex of body>
Здесь компонент query пуст (query-параметров нет), а bodyHash — SHA-256 тела запроса в hex, строчными буквами.
Пример: GET /v1/invoices/inv_74BPZFhr9qy9Uz2fbRkdJX
1709000000\nGET\n/v1/invoices/inv_74BPZFhr9qy9Uz2fbRkdJX\n\n
Query string и тело отсутствуют, поэтому последние два компонента — пустые строки.
Защита от повторной отправки
X-Request-Timestamp должен находиться в допустимом временном окне относительно серверных часов (по умолчанию ±5 минут). Запросы вне окна отклоняются как неавторизованные (timestamp_expired).
Если часы вашего сервера могут «уплывать», считайте серверное время с открытого эндпоинта /v1/time (без авторизации) и поправьте смещение перед подписью:
GET /v1/time
{ "server_time": 1739280600 }
server_time — текущее серверное время в Unix-секундах. Авторизация не нужна. Сравните его со своими часами и поправьте X-Request-Timestamp, который отправляете.
Льготный период при ротации секрета
Когда вы меняете API-секрет, прежний остаётся действительным ещё какое-то время — это льготный период. Пока он длится, принимаются подписи, вычисленные и текущим секретом, и прежним. Запустить новую ротацию нельзя, пока текущий льготный период не истёк.
Пример с SDK
Официальный SDK сам строит каноническую строку и подписывает каждый запрос. Приложение передаёт только credentials и типизированные поля запроса.
import { Paymos, externalOrderId } from '@paymos/sdk';
const paymos = new Paymos({
apiKey: process.env.PAYMOS_API_KEY,
apiSecret: process.env.PAYMOS_API_SECRET,
});
const invoice = await paymos.invoices.create({
projectId: 'prj_xxxxxxxxxxxx',
amount: '100.00',
currency: 'USD',
externalOrderId: externalOrderId('order'),
clientId: 'customer-456',
});
console.log(invoice.invoiceId, invoice.paymentUrl);
import os
from paymos import Paymos
paymos = Paymos(
api_key=os.environ["PAYMOS_API_KEY"],
api_secret=os.environ["PAYMOS_API_SECRET"],
)
invoice = paymos.invoices.create(
project_id="prj_xxxxxxxxxxxx",
amount="100.00",
currency="USD",
external_order_id="order-123",
client_id="customer-456",
)
print(invoice["invoice_id"], invoice["payment_url"])
<?php
use Paymos\Client;
use Paymos\ClientConfig;
use Paymos\IdempotencyKey;
$paymos = new Client(new ClientConfig(
getenv('PAYMOS_API_KEY'),
getenv('PAYMOS_API_SECRET')
));
$invoice = $paymos->invoices()->create(array(
'project_id' => 'prj_xxxxxxxxxxxx',
'amount' => '100.00',
'currency' => 'USD',
'external_order_id' => IdempotencyKey::externalOrderId('order'),
'client_id' => 'customer-456',
));
echo $invoice['invoice_id'] . ' ' . $invoice['payment_url'];
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)
}
invoice, err := client.Invoices.Create(context.Background(), paymos.CreateInvoiceParams{
ProjectID: "prj_xxxxxxxxxxxx",
Amount: "100.00",
Currency: "USD",
ExternalOrderID: "order-123",
ClientID: stringPointer("customer-456"),
})
if err != nil {
panic(err)
}
fmt.Println(invoice.InvoiceID, invoice.PaymentURL)
}
func stringPointer(value string) *string { return &value }
using Paymos;
using var paymos = new PaymosClient(
Environment.GetEnvironmentVariable("PAYMOS_API_KEY")!,
Environment.GetEnvironmentVariable("PAYMOS_API_SECRET")!);
var invoice = await paymos.Invoices.CreateAsync(new CreateInvoiceRequest(
ProjectId: "prj_xxxxxxxxxxxx",
Amount: "100.00",
Currency: "USD",
ExternalOrderId: "order-123",
ClientId: "customer-456"));
Console.WriteLine($"{invoice.InvoiceId} {invoice.PaymentUrl}");
import io.paymos.CreateInvoiceRequest;
import io.paymos.Invoice;
import io.paymos.PaymosClient;
PaymosClient paymos = new PaymosClient(
System.getenv("PAYMOS_API_KEY"),
System.getenv("PAYMOS_API_SECRET"));
Invoice invoice = paymos.invoices.create(
CreateInvoiceRequest.builder()
.projectId("prj_xxxxxxxxxxxx")
.amount("100.00")
.currency("USD")
.externalOrderId("order-123")
.clientId("customer-456")
.build());
System.out.println(invoice.invoiceId() + " " + invoice.paymentUrl());
require 'paymos'
paymos = Paymos::Client.new(
api_key: ENV.fetch('PAYMOS_API_KEY'),
api_secret: ENV.fetch('PAYMOS_API_SECRET')
)
invoice = paymos.invoices.create(
project_id: 'prj_xxxxxxxxxxxx',
amount: '100.00',
currency: 'USD',
external_order_id: 'order-123',
client_id: 'customer-456'
)
puts "#{invoice.invoice_id} #{invoice.payment_url}"
use paymos::{CreateInvoiceRequest, PaymosClient};
let paymos = PaymosClient::new(
std::env::var("PAYMOS_API_KEY")?,
std::env::var("PAYMOS_API_SECRET")?,
)?;
let invoice = paymos
.invoices()
.create(&CreateInvoiceRequest {
project_id: "prj_xxxxxxxxxxxx".to_owned(),
amount: "100.00".to_owned(),
currency: "USD".to_owned(),
external_order_id: "order-123".to_owned(),
network: None,
allow_multiple_payments: None,
customer_fee_percent: None,
client_id: Some("customer-456".to_owned()),
})
.await?;
println!("{} {}", invoice.invoice_id, invoice.payment_url);