Security
Authentication and payload encryption for the 155.io API
To establish a secure connection, you must:
- Provide us with whitelist IP addresses
- Implement payload encryption with a private key
Getting Started
Contact the tech team to exchange public keys before starting your integration.
Request Signing
For each request, sign the request body and include the signature in the X-Marbles-Signature header. We'll respond with a body using the same header.
Signing method: Sign all requests with RSA-SHA256 using your private key and encode to BASE64.
Implementation Example
import fs from 'fs'
import path from 'path'
import { createSign, createVerify } from 'node:crypto'
export const CryptoService = (
digestType: string,
privateKey: string,
publicKey: string
) => {
const sign = (message: string) => {
return createSign(digestType)
.update(message)
.sign(privateKey, 'base64')
}
const isValid = (message: string, signature: string) => {
return createVerify(digestType)
.update(message)
.verify(publicKey, signature, 'base64')
}
return { sign, isValid }
}
function readPem(filename: string) {
return fs
.readFileSync(path.resolve('path/to/your/pem/' + filename))
.toString('ascii')
}
// Usage
const publicKey = readPem('marbles_pub.pem')
const privateKey = readPem('your_priv.pem')
const cryptoService = CryptoService('RSA-SHA256', privateKey, publicKey)
// Sign outgoing request
const signature = cryptoService.sign(JSON.stringify(requestBody))
// Add header: X-Marbles-Signature: <signature>
// Verify incoming response
const isValid = cryptoService.isValid(responseBody, responseSignature)from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import padding
import base64
import json
class CryptoService:
def __init__(self, private_key_path: str, public_key_path: str):
with open(private_key_path, 'rb') as f:
self.private_key = serialization.load_pem_private_key(
f.read(), password=None
)
with open(public_key_path, 'rb') as f:
self.public_key = serialization.load_pem_public_key(f.read())
def sign(self, message: str) -> str:
signature = self.private_key.sign(
message.encode(),
padding.PKCS1v15(),
hashes.SHA256()
)
return base64.b64encode(signature).decode()
def is_valid(self, message: str, signature: str) -> bool:
try:
self.public_key.verify(
base64.b64decode(signature),
message.encode(),
padding.PKCS1v15(),
hashes.SHA256()
)
return True
except Exception:
return False
# Usage
crypto = CryptoService('your_priv.pem', 'marbles_pub.pem')
# Sign outgoing request
signature = crypto.sign(json.dumps(request_body))
# Add header: X-Marbles-Signature: <signature>
# Verify incoming response
is_valid = crypto.is_valid(response_body, response_signature)<?php
class CryptoService {
private $privateKey;
private $publicKey;
public function __construct(string $privateKeyPath, string $publicKeyPath) {
$this->privateKey = file_get_contents($privateKeyPath);
$this->publicKey = file_get_contents($publicKeyPath);
}
public function sign(string $message): string {
openssl_sign($message, $signature, $this->privateKey, OPENSSL_ALGO_SHA256);
return base64_encode($signature);
}
public function isValid(string $message, string $signature): bool {
return openssl_verify(
$message,
base64_decode($signature),
$this->publicKey,
OPENSSL_ALGO_SHA256
) === 1;
}
}
// Usage
$crypto = new CryptoService('your_priv.pem', 'marbles_pub.pem');
// Sign outgoing request
$signature = $crypto->sign(json_encode($requestBody));
// Add header: X-Marbles-Signature: <signature>
// Verify incoming response
$isValid = $crypto->isValid($responseBody, $responseSignature);import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.*;
import java.security.spec.*;
import java.util.Base64;
public class CryptoService {
private final PrivateKey privateKey;
private final PublicKey publicKey;
public CryptoService(String privateKeyPath, String publicKeyPath) throws Exception {
this.privateKey = loadPrivateKey(privateKeyPath);
this.publicKey = loadPublicKey(publicKeyPath);
}
public String sign(String message) throws Exception {
Signature signature = Signature.getInstance("SHA256withRSA");
signature.initSign(privateKey);
signature.update(message.getBytes());
return Base64.getEncoder().encodeToString(signature.sign());
}
public boolean isValid(String message, String signatureStr) {
try {
Signature signature = Signature.getInstance("SHA256withRSA");
signature.initVerify(publicKey);
signature.update(message.getBytes());
return signature.verify(Base64.getDecoder().decode(signatureStr));
} catch (Exception e) {
return false;
}
}
// Load keys from PEM files (implement based on your key format)
private PrivateKey loadPrivateKey(String path) throws Exception { /* ... */ }
private PublicKey loadPublicKey(String path) throws Exception { /* ... */ }
}
// Usage
CryptoService crypto = new CryptoService("your_priv.pem", "marbles_pub.pem");
String signature = crypto.sign(jsonBody);
boolean isValid = crypto.isValid(responseBody, responseSignature);using System;
using System.Security.Cryptography;
using System.Text;
public class CryptoService
{
private readonly RSA _privateKey;
private readonly RSA _publicKey;
public CryptoService(string privateKeyPath, string publicKeyPath)
{
_privateKey = RSA.Create();
_privateKey.ImportFromPem(File.ReadAllText(privateKeyPath));
_publicKey = RSA.Create();
_publicKey.ImportFromPem(File.ReadAllText(publicKeyPath));
}
public string Sign(string message)
{
byte[] data = Encoding.UTF8.GetBytes(message);
byte[] signature = _privateKey.SignData(
data, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
return Convert.ToBase64String(signature);
}
public bool IsValid(string message, string signature)
{
try
{
byte[] data = Encoding.UTF8.GetBytes(message);
byte[] signatureBytes = Convert.FromBase64String(signature);
return _publicKey.VerifyData(
data, signatureBytes, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
}
catch
{
return false;
}
}
}
// Usage
var crypto = new CryptoService("your_priv.pem", "marbles_pub.pem");
string signature = crypto.Sign(jsonBody);
bool isValid = crypto.IsValid(responseBody, responseSignature);Invalid Signature Response
If signature validation fails, you will receive an error:
HTTP/1.1 400 Bad Request
X-Marbles-Signature: <signature>
Content-Type: application/json
{
"status": "INVALID_SIGNATURE"
}Timeouts
The goal is to achieve sub-second communication between our systems. However, we set a 5-second timeout as a fallback in case a process fails to respond.
Idempotency
Important
All requests must be processed in an idempotent way. If a request is executed multiple times, it should return the same response (even if the request ID differs).
If a duplicate transaction is detected with a different payload, return a DUPLICATE_TRANSACTION_ERROR.