155API

Security

Authentication and payload encryption for the 155.io API


To establish a secure connection, you must:

  1. Provide us with whitelist IP addresses
  2. 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.

On this page