Authentication
The Pago46 API signs every request with HMAC SHA-256. You compute a signature from your secret key and the request contents; the server recomputes it and compares. A match confirms two things: that the request is yours and that nobody altered the contents in transit.
The scheme is identical for Merchants and Payment Providers. The only thing that changes is the name of the header where you send your public key.
Required headers
Every HTTP request must include these three headers:
| Header | Description |
|---|---|
Merchant-Key or Provider-Key | Your public key. Use Merchant-Key if you are a Merchant and Provider-Key if you are a Payment Provider. |
Message-Date | Unix timestamp in seconds (integer or decimal). |
Message-Hash | The HMAC SHA-256 signature in hexadecimal (see below). |
The server rejects the request if Message-Date differs from the server time by more than 5 minutes, to prevent replay attacks. Always use the current time, in seconds: sending milliseconds puts it outside the window and the request is rejected.
Building the signature
1. String to sign
Concatenate these five values separated by colons (:), in this exact order:
KEY:MESSAGE_DATE:METHOD:PATH:BODY
- Public key — the same value as your
Merchant-KeyorProvider-Keyheader. - Message-Date — the same value you send in the header.
- HTTP method —
POST,GET, etc. - Path — the resource path without the query string, for example
/api/v1/merchants/orders/pay-in/. - Body — the raw request body in UTF-8. If the request has no body (for example a
GET), use an empty string.
Parameters are not sorted and the JSON is not normalized. Sign exactly the same bytes you send in the body: any extra space or reordering produces a different signature.
2. Compute the hash
Sign the string with HMAC SHA-256 using your secret as the key, and output the result in hexadecimal (hexdigest).
Implementation examples
The same signer works for Merchants and Providers; only the key header name changes.
- Python
- Node.js
- Go
import hashlib
import hmac
import json
import time
import requests
HOST = "https://api.dev.pago46.io"
def signed_request(key, secret, key_header, method, path, body=None):
timestamp = str(time.time()) # Unix in seconds
body_str = json.dumps(body) if body else ""
string_to_sign = f"{key}:{timestamp}:{method}:{path}:{body_str}"
signature = hmac.new(
secret.encode("utf-8"),
string_to_sign.encode("utf-8"),
hashlib.sha256,
).hexdigest()
headers = {
key_header: key, # "Merchant-Key" or "Provider-Key"
"Message-Date": timestamp,
"Message-Hash": signature,
"Content-Type": "application/json",
}
return requests.request(method, f"{HOST}{path}", headers=headers, data=body_str)
# Example: a Merchant creates a pay-in order
response = signed_request(
key="YOUR_MERCHANT_KEY",
secret="YOUR_MERCHANT_SECRET",
key_header="Merchant-Key",
method="POST",
path="/api/v1/merchants/orders/pay-in/",
body={"order_type": "LocalCurrencyOrder", "price": "100.00", "price_currency": "CLP"},
)
print(response.status_code)
const crypto = require('crypto');
const axios = require('axios');
const HOST = 'https://api.dev.pago46.io';
async function signedRequest({ key, secret, keyHeader, method, path, body = null }) {
const timestamp = (Date.now() / 1000).toString(); // Unix in seconds
const bodyStr = body ? JSON.stringify(body) : '';
const stringToSign = [key, timestamp, method, path, bodyStr].join(':');
const signature = crypto
.createHmac('sha256', secret)
.update(stringToSign)
.digest('hex');
return axios({
method,
url: `${HOST}${path}`,
headers: {
[keyHeader]: key, // 'Merchant-Key' or 'Provider-Key'
'Message-Date': timestamp,
'Message-Hash': signature,
'Content-Type': 'application/json',
},
data: bodyStr,
});
}
package main
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"strconv"
"strings"
"time"
)
// keyHeader is "Merchant-Key" or "Provider-Key".
func authHeaders(key, secret, keyHeader, method, path, bodyStr string) map[string]string {
timestamp := strconv.FormatInt(time.Now().Unix(), 10) // Unix in seconds
stringToSign := strings.Join([]string{key, timestamp, method, path, bodyStr}, ":")
h := hmac.New(sha256.New, []byte(secret))
h.Write([]byte(stringToSign))
signature := hex.EncodeToString(h.Sum(nil))
return map[string]string{
keyHeader: key,
"Message-Date": timestamp,
"Message-Hash": signature,
}
}
Authentication errors
When authentication fails, the API returns 403 Forbidden with the standard error format:
{
"type": "client_error",
"errors": [
{
"code": "authentication_failed",
"detail": "Incorrect authentication credentials.",
"attr": null
}
]
}
| Probable cause | How to fix it |
|---|---|
| A required header is missing | Include Merchant-Key/Provider-Key, Message-Date and Message-Hash. |
Message-Date outside the 5-minute window | Use the current system time, in seconds. |
| Unknown public key | Make sure you are using the key for the correct environment (sandbox vs. production). |
| The signature does not match | Check the string order, the : separator, the path without query string, and that the body is identical to the one sent. |
Choose your guide
I'm a Merchant
I want to collect payments or send money to my users.
I'm a Payment Provider
I have physical points and want to process Pago46 transactions.