Authentication
Sign every API request with an RSA key pair using HTTP Message Signatures (RFC 9421).
BEEM authenticates every API request with an RSA digital signature over the request itself, following the HTTP Message Signatures standard (RFC 9421).
This is stronger than a static bearer token: the signature covers the HTTP method, target URL, request body and a timestamp, so a captured request cannot be tampered with or replayed.
Three things you need before your first call
- An RSA key pair generated locally.
- The public key registered against an API client in the Portal.
- The
clientIdthat the Portal returns once the public key is saved.
How it works
| Component | Lives | Purpose |
|---|---|---|
| Private key | In your application (vault / HSM / secret manager) | Signs each outbound request. Never leaves your environment. |
| Public key | Registered against an API client in the BEEM Portal | Used by BEEM to verify your signature. |
clientId | Issued by the Portal when the public key is saved | Identifies which registered key signed a given request. Sent in the Signature-Input header as keyid. |
Each request carries four signature-related headers: Content-Digest, Date, Signature-Input, and Signature. BEEM verifies them on every call.
Step 1 — Generate an RSA key pair
Run the two commands below on the machine that will host your integration. The private key never leaves that environment.
- Generate the private key. Keep this file secret — anyone with it can sign requests as you.
openssl genrsa -out api-client-key.pem 2048- Extract the public key. This is the only file you will upload to BEEM.
openssl rsa -in api-client-key.pem -pubout -out api-client-pub.pem
Treatapi-client-key.pemlike a passwordStore it in a secrets manager (AWS Secrets Manager, HashiCorp Vault, 1Password, etc.) and restrict file permissions on disk with
chmod 600 api-client-key.pem. If the private key is ever exposed, rotate it immediately by registering a new public key in the Portal and removing the old one.
Step 2 — Register the public key in the Portal
The public key is registered against an API client on your account. Each client carries its own public key, IP allowlist and role assignment.
-
Sign in to the Account Portal.
-
Go to Settings → Clients.
-
Click +Create a Client to create a new client (or click Edit on an existing client to attach the key to it).

Settings → Clients. Click + Create a client to start a new one.

Settings → Clients. Click the pencil icon to edit an existing client.
-
Complete the fields:
-
Name — a label that identifies the integration (e.g.
prod-checkout). -
Network whitelist — the IP addresses allowed to use this client. Click Add for each. To allow all IPs, enter
0.0.0.0.
Network whitelist. Each address must be in CIDR format.
-
Public key — paste the contents of
api-client-public-key.pem, including the-----BEGIN PUBLIC KEY-----and-----END PUBLIC KEY-----lines.
Paste the full PEM block, including the BEGIN and END lines.
-
Assign roles to user — pick the roles that scope what this client can do (e.g. read-only, payments, withdrawals).

Assign roles, confirm Enabled is on, then click Add.
-
-
Click Add (or Update, if editing).
Restrict the IP allow list whenever possible
0.0.0.0allows traffic from anywhere. Use it only for early sandbox testing. Production clients should list the static egress IPs of your servers.
Step 3 — Get your clientId
clientIdThe clientId is the identifier BEEM uses to look up the public key when verifying a signature. It is sent on every request as the keyid parameter inside Signature-Input.
-
In the Portal, go to Settings → Clients.
-
Find the client created in the previous step.
-
Click the copy icon next to its
clientIdand store it alongside the private key.
The clientId is shown in the Overview side panel and is also visible in the Client Id column of the Clients list.
No access to Settings → Clients?If your role does not allow you to manage clients, send the public key and the IP addresses to your Solutions Manager and ask them to register the client and return the
clientId.
Step 4 — Sign each request
Every signed request must include four headers:
| Header | What it contains |
|---|---|
Content-Digest | The SHA-256 digest of the request body, base64-encoded. Only present on requests with a body. |
Date | The current time in HTTP-date format (RFC 7231). Used to mitigate replay attacks. |
Signature-Input | The list of signed components, the creation timestamp, the keyid (your clientId), and the signature algorithm (rsa-v1_5-sha256). |
Signature | The base64-encoded RSA signature over the canonical signature base. |
The signed components are:
@method— the HTTP method (POST,GET, …).@target-uri— the full request URL.content-digest— the digest header above (omitted when there is no body).date— the date header above.
Example signed request
POST /api/transaction HTTP/1.1
Host: api.sandbox.layer1.com
Date: Tue, 27 May 2025 10:21:54 GMT
Content-Type: application/json
Content-Digest: sha-256=:W6ph5Mm5Pz8GgiULbPgzG37mj9g=:
Signature-Input: sig=("@method" "@target-uri" "content-digest" "date");created=1716792114;keyid="client-123";alg="rsa-v1_5-sha256"
Signature: sig=:MEUCIQDn...==:Reference signers
The classes below provide the same interface in six languages. The constructor takes (privateKey, clientId); the single public method buildHeaders(url, payload, method) returns the four signature headers (Content-Digest, Date, Signature-Input, Signature). All six produce byte-identical signatures for the same input, so you can use any of them to call the same BEEM endpoints.
Private key formatEvery signer expects the private key in PKCS#8 format (the PEM block starts with
-----BEGIN PRIVATE KEY-----). If your version of OpenSSL produced a PKCS#1 key (-----BEGIN RSA PRIVATE KEY-----), convert it once with:openssl pkcs8 -topk8 -nocrypt -in api-client-key.pem -out api-client-key-pkcs8.pem
import java.nio.charset.StandardCharsets;
import java.security.KeyFactory;
import java.security.MessageDigest;
import java.security.PrivateKey;
import java.security.Signature;
import java.security.spec.PKCS8EncodedKeySpec;
import java.time.Instant;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
import java.util.Base64;
import java.util.LinkedHashMap;
import java.util.Locale;
import java.util.Map;
public class HttpSigner {
private static final String SIGNATURE_ALGORITHM = "rsa-v1_5-sha256";
private static final String DIGEST_ALGORITHM = "sha-256";
private static final DateTimeFormatter HTTP_DATE =
DateTimeFormatter.ofPattern("EEE, dd MMM yyyy HH:mm:ss 'GMT'", Locale.US)
.withZone(ZoneOffset.UTC);
private final PrivateKey privateKey;
private final String clientId;
public HttpSigner(String base64PrivateKey, String clientId) throws Exception {
this.clientId = clientId;
this.privateKey = loadPrivateKey(base64PrivateKey);
}
private PrivateKey loadPrivateKey(String rawKey) throws Exception {
String cleanKey = rawKey
.replace("-----BEGIN PRIVATE KEY-----", "")
.replace("-----END PRIVATE KEY-----", "")
.replaceAll("\\s+", "");
byte[] keyBytes = Base64.getDecoder().decode(cleanKey);
PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(keyBytes);
KeyFactory kf = KeyFactory.getInstance("RSA");
return kf.generatePrivate(spec);
}
public Map<String, String> buildHeaders(String url, String payload, String method) throws Exception {
Map<String, String> headers = new LinkedHashMap<>();
String contentDigest = null;
if (payload != null && !payload.isEmpty()) {
contentDigest = createDigest(DIGEST_ALGORITHM, payload);
headers.put("Content-Digest", contentDigest);
}
long now = Instant.now().getEpochSecond();
String dateHeader = HTTP_DATE.format(Instant.ofEpochSecond(now));
headers.put("Date", dateHeader);
String signatureParameters = createSignatureParameters(contentDigest, now);
headers.put("Signature-Input", "sig=" + signatureParameters);
StringBuilder base = new StringBuilder();
base.append("\"@method\": ").append(method.toUpperCase(Locale.US)).append("\n");
base.append("\"@target-uri\": ").append(url).append("\n");
if (contentDigest != null) {
base.append("\"content-digest\": ").append(contentDigest).append("\n");
}
base.append("\"date\": ").append(dateHeader).append("\n");
base.append("\"@signature-params\": ").append(signatureParameters);
String signature = sign(base.toString());
headers.put("Signature", "sig=:" + signature + ":");
return headers;
}
private String createSignatureParameters(String contentDigest, long createdTimestamp) {
StringBuilder components = new StringBuilder("(\"@method\" \"@target-uri\"");
if (contentDigest != null) {
components.append(" \"content-digest\"");
}
components.append(" \"date\"");
return String.format(
"%s);created=%d;keyid=\"%s\";alg=\"%s\"",
components,
createdTimestamp,
clientId,
SIGNATURE_ALGORITHM
);
}
private String sign(String signatureBase) throws Exception {
Signature signer = Signature.getInstance("SHA256withRSA");
signer.initSign(privateKey);
signer.update(signatureBase.getBytes(StandardCharsets.UTF_8));
return Base64.getEncoder().encodeToString(signer.sign());
}
private String createDigest(String digestAlgorithm, String data) throws Exception {
return String.format("%s=:%s:", digestAlgorithm, getDigest(digestAlgorithm, data));
}
private String getDigest(String algorithm, String data) throws Exception {
MessageDigest md = MessageDigest.getInstance(algorithm.toUpperCase(Locale.US));
byte[] hash = md.digest(data.getBytes(StandardCharsets.UTF_8));
return Base64.getEncoder().encodeToString(hash);
}
}class HttpSigner {
private const SIGNATURE_ALGORITHM = 'rsa-v1_5-sha256';
private const DIGEST_ALGORITHM = 'sha-256';
private $privateKey;
private $clientId;
public function __construct(string $base64PrivateKey, string $clientId) {
$this->clientId = $clientId;
$this->privateKey = $this->loadPrivateKey($base64PrivateKey);
}
private function loadPrivateKey(string $key) {
$preparedKey = $this->prepareKey($key);
$privateKey = openssl_pkey_get_private($preparedKey);
if ($privateKey === false) {
throw new RuntimeException('Failed to load private key: ' . openssl_error_string());
}
return $privateKey;
}
private function prepareKey(string $rawKey): string {
if (strpos($rawKey, '-----BEGIN PRIVATE KEY-----') !== false) {
return $rawKey;
}
$cleanKey = preg_replace('/\s+/', '', $rawKey);
return "-----BEGIN PRIVATE KEY-----\n" .
chunk_split($cleanKey, 64, "\n") .
"-----END PRIVATE KEY-----\n";
}
public function buildHeaders(string $url, ?string $payload, string $method): array {
$headers = [];
$contentDigest = null;
if (!empty($payload)) {
$contentDigest = $this->createDigest(self::DIGEST_ALGORITHM, $payload);
$headers['Content-Digest'] = $contentDigest;
}
$now = time();
$dateHeader = gmdate('D, d M Y H:i:s', $now) . ' GMT';
$headers['Date'] = $dateHeader;
$signatureParameters = $this->createSignatureParameters($contentDigest, $now);
$headers['Signature-Input'] = 'sig=' . $signatureParameters;
$signatureBase = sprintf(
"\"@method\": %s\n\"@target-uri\": %s\n%s\"date\": %s\n\"@signature-params\": %s",
strtoupper($method),
$url,
$contentDigest === null ? '' : "\"content-digest\": " . $contentDigest . "\n",
$dateHeader,
$signatureParameters
);
$signature = $this->sign($signatureBase);
$headers['Signature'] = sprintf('sig=:%s:', $signature);
return $headers;
}
private function createSignatureParameters(?string $contentDigest, int $createdTimestamp): string {
$components = '("@method" "@target-uri"';
if ($contentDigest !== null) {
$components .= ' "content-digest"';
}
$components .= ' "date"';
return sprintf(
'%s);created=%d;keyid="%s";alg="%s"',
$components,
$createdTimestamp,
$this->clientId,
self::SIGNATURE_ALGORITHM
);
}
private function sign(string $signatureBase): string {
$signature = '';
$success = openssl_sign(
$signatureBase,
$signature,
$this->privateKey,
OPENSSL_ALGO_SHA256
);
if (!$success) {
throw new RuntimeException('Failed to sign request: ' . openssl_error_string());
}
return base64_encode($signature);
}
private function createDigest(string $digestAlgorithm, string $data): string {
return sprintf('%s=:%s:', $digestAlgorithm, $this->getDigest($digestAlgorithm, $data));
}
private function getDigest(string $algorithm, string $data): string {
$phpAlgorithm = str_replace('-', '', $algorithm);
$hash = hash($phpAlgorithm, $data, true);
return base64_encode($hash);
}
}using System;
using System.Collections.Generic;
using System.Globalization;
using System.Security.Cryptography;
using System.Text;
public class HttpSigner
{
private const string SignatureAlgorithm = "rsa-v1_5-sha256";
private const string DigestAlgorithm = "sha-256";
private readonly RSA _privateKey;
private readonly string _clientId;
public HttpSigner(string base64PrivateKey, string clientId)
{
_clientId = clientId;
_privateKey = LoadPrivateKey(base64PrivateKey);
}
private static RSA LoadPrivateKey(string rawKey)
{
string cleanKey = rawKey
.Replace("-----BEGIN PRIVATE KEY-----", "")
.Replace("-----END PRIVATE KEY-----", "")
.Replace("\r", "").Replace("\n", "").Replace(" ", "");
byte[] keyBytes = Convert.FromBase64String(cleanKey);
var rsa = RSA.Create();
rsa.ImportPkcs8PrivateKey(keyBytes, out _);
return rsa;
}
public Dictionary<string, string> BuildHeaders(string url, string payload, string method)
{
var headers = new Dictionary<string, string>();
string contentDigest = null;
if (!string.IsNullOrEmpty(payload))
{
contentDigest = CreateDigest(DigestAlgorithm, payload);
headers["Content-Digest"] = contentDigest;
}
long now = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
string dateHeader = DateTimeOffset.FromUnixTimeSeconds(now)
.ToString("ddd, dd MMM yyyy HH:mm:ss 'GMT'", CultureInfo.InvariantCulture);
headers["Date"] = dateHeader;
string signatureParameters = CreateSignatureParameters(contentDigest, now);
headers["Signature-Input"] = "sig=" + signatureParameters;
var sb = new StringBuilder();
sb.Append($"\"@method\": {method.ToUpperInvariant()}\n");
sb.Append($"\"@target-uri\": {url}\n");
if (contentDigest != null)
{
sb.Append($"\"content-digest\": {contentDigest}\n");
}
sb.Append($"\"date\": {dateHeader}\n");
sb.Append($"\"@signature-params\": {signatureParameters}");
string signature = Sign(sb.ToString());
headers["Signature"] = $"sig=:{signature}:";
return headers;
}
private string CreateSignatureParameters(string contentDigest, long createdTimestamp)
{
var sb = new StringBuilder("(\"@method\" \"@target-uri\"");
if (contentDigest != null)
{
sb.Append(" \"content-digest\"");
}
sb.Append(" \"date\"");
return $"{sb});created={createdTimestamp};keyid=\"{_clientId}\";alg=\"{SignatureAlgorithm}\"";
}
private string Sign(string signatureBase)
{
byte[] data = Encoding.UTF8.GetBytes(signatureBase);
byte[] signature = _privateKey.SignData(data, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
return Convert.ToBase64String(signature);
}
private static string CreateDigest(string digestAlgorithm, string data)
{
using var sha = SHA256.Create();
byte[] hash = sha.ComputeHash(Encoding.UTF8.GetBytes(data));
return $"{digestAlgorithm}=:{Convert.ToBase64String(hash)}:";
}
}import base64
import time
from email.utils import formatdate
from hashlib import sha256
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import padding
class HttpSigner:
SIGNATURE_ALGORITHM = "rsa-v1_5-sha256"
DIGEST_ALGORITHM = "sha-256"
def __init__(self, base64_private_key, client_id):
self.client_id = client_id
self.private_key = self._load_private_key(base64_private_key)
@staticmethod
def _load_private_key(raw_key):
if "-----BEGIN PRIVATE KEY-----" not in raw_key:
clean = "".join(raw_key.split())
raw_key = (
"-----BEGIN PRIVATE KEY-----\n"
+ "\n".join(clean[i:i + 64] for i in range(0, len(clean), 64))
+ "\n-----END PRIVATE KEY-----\n"
)
return serialization.load_pem_private_key(raw_key.encode("utf-8"), password=None)
def build_headers(self, url, payload, method):
headers = {}
content_digest = None
if payload:
content_digest = self._create_digest(self.DIGEST_ALGORITHM, payload)
headers["Content-Digest"] = content_digest
now = int(time.time())
date_header = formatdate(now, usegmt=True)
headers["Date"] = date_header
signature_parameters = self._create_signature_parameters(content_digest, now)
headers["Signature-Input"] = "sig=" + signature_parameters
parts = [
f'"@method": {method.upper()}',
f'"@target-uri": {url}',
]
if content_digest is not None:
parts.append(f'"content-digest": {content_digest}')
parts.append(f'"date": {date_header}')
parts.append(f'"@signature-params": {signature_parameters}')
signature_base = "\n".join(parts)
signature = self._sign(signature_base)
headers["Signature"] = f"sig=:{signature}:"
return headers
def _create_signature_parameters(self, content_digest, created_timestamp):
components = '("@method" "@target-uri"'
if content_digest is not None:
components += ' "content-digest"'
components += ' "date"'
return (
f'{components});'
f'created={created_timestamp};'
f'keyid="{self.client_id}";'
f'alg="{self.SIGNATURE_ALGORITHM}"'
)
def _sign(self, signature_base):
signature = self.private_key.sign(
signature_base.encode("utf-8"),
padding.PKCS1v15(),
hashes.SHA256(),
)
return base64.b64encode(signature).decode("ascii")
@staticmethod
def _create_digest(digest_algorithm, data):
digest = base64.b64encode(sha256(data.encode("utf-8")).digest()).decode("ascii")
return f"{digest_algorithm}=:{digest}:"const crypto = require('crypto');
class HttpSigner {
static SIGNATURE_ALGORITHM = 'rsa-v1_5-sha256';
static DIGEST_ALGORITHM = 'sha-256';
constructor(base64PrivateKey, clientId) {
this.clientId = clientId;
this.privateKey = HttpSigner.#loadPrivateKey(base64PrivateKey);
}
static #loadPrivateKey(rawKey) {
if (!rawKey.includes('-----BEGIN PRIVATE KEY-----')) {
const clean = rawKey.replace(/\s+/g, '');
const chunks = clean.match(/.{1,64}/g).join('\n');
rawKey = `-----BEGIN PRIVATE KEY-----\n${chunks}\n-----END PRIVATE KEY-----\n`;
}
return crypto.createPrivateKey({ key: rawKey, format: 'pem' });
}
buildHeaders(url, payload, method) {
const headers = {};
let contentDigest = null;
if (payload) {
contentDigest = this.#createDigest(HttpSigner.DIGEST_ALGORITHM, payload);
headers['Content-Digest'] = contentDigest;
}
const now = Math.floor(Date.now() / 1000);
const dateHeader = new Date(now * 1000).toUTCString();
headers['Date'] = dateHeader;
const signatureParameters = this.#createSignatureParameters(contentDigest, now);
headers['Signature-Input'] = `sig=${signatureParameters}`;
const parts = [
`"@method": ${method.toUpperCase()}`,
`"@target-uri": ${url}`,
];
if (contentDigest !== null) parts.push(`"content-digest": ${contentDigest}`);
parts.push(`"date": ${dateHeader}`);
parts.push(`"@signature-params": ${signatureParameters}`);
const signatureBase = parts.join('\n');
const signature = this.#sign(signatureBase);
headers['Signature'] = `sig=:${signature}:`;
return headers;
}
#createSignatureParameters(contentDigest, createdTimestamp) {
let components = '("@method" "@target-uri"';
if (contentDigest !== null) components += ' "content-digest"';
components += ' "date"';
return `${components});created=${createdTimestamp};keyid="${this.clientId}";alg="${HttpSigner.SIGNATURE_ALGORITHM}"`;
}
#sign(signatureBase) {
const signer = crypto.createSign('RSA-SHA256');
signer.update(signatureBase);
return signer.sign(this.privateKey, 'base64');
}
#createDigest(digestAlgorithm, data) {
const hash = crypto.createHash('sha256').update(data).digest('base64');
return `${digestAlgorithm}=:${hash}:`;
}
}
module.exports = HttpSigner;require 'base64'
require 'digest'
require 'openssl'
class HttpSigner
SIGNATURE_ALGORITHM = 'rsa-v1_5-sha256'
DIGEST_ALGORITHM = 'sha-256'
def initialize(base64_private_key, client_id)
@client_id = client_id
@private_key = load_private_key(base64_private_key)
end
def build_headers(url, payload, method)
headers = {}
content_digest = nil
if payload && !payload.empty?
content_digest = create_digest(DIGEST_ALGORITHM, payload)
headers['Content-Digest'] = content_digest
end
now = Time.now.to_i
date_header = Time.at(now).utc.strftime('%a, %d %b %Y %H:%M:%S GMT')
headers['Date'] = date_header
signature_parameters = create_signature_parameters(content_digest, now)
headers['Signature-Input'] = "sig=#{signature_parameters}"
parts = [
"\"@method\": #{method.upcase}",
"\"@target-uri\": #{url}"
]
parts << "\"content-digest\": #{content_digest}" if content_digest
parts << "\"date\": #{date_header}"
parts << "\"@signature-params\": #{signature_parameters}"
signature_base = parts.join("\n")
signature = sign(signature_base)
headers['Signature'] = "sig=:#{signature}:"
headers
end
private
def load_private_key(raw_key)
unless raw_key.include?('-----BEGIN PRIVATE KEY-----')
clean = raw_key.gsub(/\s+/, '')
raw_key = "-----BEGIN PRIVATE KEY-----\n" +
clean.scan(/.{1,64}/).join("\n") +
"\n-----END PRIVATE KEY-----\n"
end
OpenSSL::PKey::RSA.new(raw_key)
end
def create_signature_parameters(content_digest, created_timestamp)
components = '("@method" "@target-uri"'
components += ' "content-digest"' if content_digest
components += ' "date"'
"#{components});created=#{created_timestamp};keyid=\"#{@client_id}\";alg=\"#{SIGNATURE_ALGORITHM}\""
end
def sign(signature_base)
signature = @private_key.sign(OpenSSL::Digest::SHA256.new, signature_base)
Base64.strict_encode64(signature)
end
def create_digest(digest_algorithm, data)
hash = Base64.strict_encode64(Digest::SHA256.digest(data))
"#{digest_algorithm}=:#{hash}:"
end
endRuntime requirements
| Language | Requirement |
|---|---|
| Java | JDK 8 or later. No external dependencies — uses javax.crypto from the standard library. |
| PHP | PHP 7.4 or later. Requires the openssl extension (enabled by default in most distributions). |
| C# | .NET 5 or later (for RSA.ImportPkcs8PrivateKey). For .NET Framework, use BouncyCastle to import the key. |
| Python | Python 3.7+ and the cryptography package: pip install cryptography. |
| Node.js / JavaScript | Node.js 16+. Uses the built-in crypto module — no npm install needed. |
| Ruby | Ruby 2.5+. Uses standard library openssl, base64, digest. |
Using the signer
Once you have an HttpSigner instance, every API call is the same shape: build the headers, attach them to your HTTP client, send the request. The example below makes a POST /api/transaction call to BEEM Production.
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Map;
public class SignedRequestExample {
public static void main(String[] args) throws Exception {
String privateKey = new String(Files.readAllBytes(Paths.get("/path/to/api-client-key.pem")));
String clientId = "0193abc8-74c5-785f-ae34-11ab890b2681";
HttpSigner signer = new HttpSigner(privateKey, clientId);
String method = "POST";
String url = "https://api.core.paybeem.com/api/transaction";
String payload = "{\"amount\":100,\"currency\":\"USDC\"}";
Map<String, String> headers = signer.buildHeaders(url, payload, method);
HttpRequest.Builder requestBuilder = HttpRequest.newBuilder(URI.create(url))
.POST(HttpRequest.BodyPublishers.ofString(payload))
.header("Content-Type", "application/json");
headers.forEach(requestBuilder::header);
HttpResponse<String> response = HttpClient.newHttpClient()
.send(requestBuilder.build(), HttpResponse.BodyHandlers.ofString());
System.out.println(response.statusCode());
System.out.println(response.body());
}
}<?php
$privateKey = file_get_contents('/path/to/api-client-key.pem');
$clientId = '0193abc8-74c5-785f-ae34-11ab890b2681';
$signer = new HttpSigner($privateKey, $clientId);
$method = 'POST';
$url = 'https://api.core.paybeem.com/api/transaction';
$payload = json_encode(['amount' => 100, 'currency' => 'USDC']);
$headers = $signer->buildHeaders($url, $payload, $method);
$curlHeaders = ['Content-Type: application/json'];
foreach ($headers as $name => $value) {
$curlHeaders[] = "$name: $value";
}
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => $method,
CURLOPT_POSTFIELDS => $payload,
CURLOPT_HTTPHEADER => $curlHeaders,
CURLOPT_RETURNTRANSFER => true,
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
echo $status . "\n";
echo $response;using System;
using System.IO;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
class Program
{
static async Task Main()
{
string privateKey = File.ReadAllText("/path/to/api-client-key.pem");
string clientId = "0193abc8-74c5-785f-ae34-11ab890b2681";
var signer = new HttpSigner(privateKey, clientId);
string method = "POST";
string url = "https://api.core.paybeem.com/api/transaction";
string payload = "{\"amount\":100,\"currency\":\"USDC\"}";
var headers = signer.BuildHeaders(url, payload, method);
using var http = new HttpClient();
var request = new HttpRequestMessage(new HttpMethod(method), url)
{
Content = new StringContent(payload, Encoding.UTF8, "application/json")
};
foreach (var header in headers)
{
request.Headers.TryAddWithoutValidation(header.Key, header.Value);
}
var response = await http.SendAsync(request);
Console.WriteLine((int)response.StatusCode);
Console.WriteLine(await response.Content.ReadAsStringAsync());
}
}import json
import requests
with open("/path/to/api-client-key.pem") as f:
private_key = f.read()
client_id = "0193abc8-74c5-785f-ae34-11ab890b2681"
signer = HttpSigner(private_key, client_id)
method = "POST"
url = "https://api.core.paybeem.com/api/transaction"
payload = json.dumps({"amount": 100, "currency": "USDC"})
headers = signer.build_headers(url, payload, method)
headers["Content-Type"] = "application/json"
response = requests.request(method, url, data=payload, headers=headers)
print(response.status_code)
print(response.text)const fs = require('fs');
const HttpSigner = require('./HttpSigner');
const privateKey = fs.readFileSync('/path/to/api-client-key.pem', 'utf8');
const clientId = '0193abc8-74c5-785f-ae34-11ab890b2681';
const signer = new HttpSigner(privateKey, clientId);
const method = 'POST';
const url = 'https://api.core.paybeem.com/api/transaction';
const payload = JSON.stringify({ amount: 100, currency: 'USDC' });
const headers = signer.buildHeaders(url, payload, method);
headers['Content-Type'] = 'application/json';
(async () => {
const response = await fetch(url, { method, headers, body: payload });
console.log(response.status);
console.log(await response.text());
})();require 'json'
require 'net/http'
require 'uri'
private_key = File.read('/path/to/api-client-key.pem')
client_id = '0193abc8-74c5-785f-ae34-11ab890b2681'
signer = HttpSigner.new(private_key, client_id)
method = 'POST'
url = 'https://api.core.paybeem.com/api/transaction'
payload = { amount: 100, currency: 'USDC' }.to_json
headers = signer.build_headers(url, payload, method)
headers['Content-Type'] = 'application/json'
uri = URI(url)
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Post.new(uri.request_uri, headers)
request.body = payload
response = http.request(request)
puts response.code
puts response.bodyArguments
| Argument | Description |
|---|---|
privateKey | Your RSA private key, loaded from disk or a secrets manager. The signer accepts a full PEM block (including the BEGIN/END lines) or just the Base64 body. |
clientId | The Client Id shown in the Portal under Settings → Clients. |
url | The full request URL, including scheme, host and path. Must exactly match the URL the request is sent to — otherwise the signature will not verify. |
payload | The raw request body as a string. Pass null (or the language's equivalent: None, nil, omit the argument) for GET requests or any request with no body. |
method | The HTTP method (GET, POST, PUT, DELETE, etc.). Case is normalised inside the signer. |
The URL must match exactlyThe signature covers the full URL including query string. If your HTTP client appends or re-orders query parameters (some clients do), the server will reject the request with a signature mismatch. Build the final URL once, pass it to
buildHeaders, and pass the same URL to your HTTP client without further modification.
Troubleshooting
If BEEM rejects a signed request, check the following in order:
clientIdmatches the one shown in the Portal (no leading or trailing whitespace).- The public key registered in the Portal corresponds to the private key in the signer — not the other way around.
- The IP the request originates from is in the client's network whitelist.
- The
Dateheader is within a few minutes of the current UTC time. Skewed clocks cause replay-protection failures. - The
Content-Digestis computed over the exact bytes sent in the body — do not pretty-print or re-serialise the JSON between digest and send. - The signature base matches the format BEEM expects (see the
buildHeadersmethod above). - The role assigned to the client allows the operation being attempted.

