Skip to content

Integration Guide

Get PulseRoute integrated into your payment service in 30 minutes.

Choose Your Path

I use... Start here
Node.js / TypeScript Node.js Integration
Python Python Integration
Java / Kotlin Java Integration
Any language REST API Integration

Node.js

Install

npm install @pulseroute/sdk

Initialize

const PulseRoute = require('@pulseroute/sdk');

const client = PulseRoute.init({
  apiKey: process.env.PULSEROUTE_API_KEY,
  baseUrl: process.env.PULSEROUTE_URL || 'http://localhost:8080',
});

Stripe + Adyen Example

const Stripe = require('stripe');
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY);

async function processPayment(order) {
  // 1. Ask PulseRoute where to route
  const decision = await client.getRoute({
    country: order.country,
    currency: order.currency,
    paymentMethod: 'card',
    amount: order.amount,
  });

  const start = Date.now();
  let success = false;
  let errorCode = null;

  try {
    if (decision.processorId === 'stripe') {
      const intent = await stripe.paymentIntents.create({
        amount: Math.round(order.amount * 100),
        currency: order.currency.toLowerCase(),
        payment_method: order.paymentMethodId,
        confirm: true,
      });
      success = intent.status === 'succeeded';
      if (!success) errorCode = intent.last_payment_error?.code;

    } else if (decision.processorId === 'adyen') {
      const result = await adyenCheckout.payments({
        amount: { value: Math.round(order.amount * 100), currency: order.currency },
        reference: order.id,
        paymentMethod: order.adyenPaymentData,
        merchantAccount: process.env.ADYEN_MERCHANT,
      });
      success = result.resultCode === 'Authorised';
      if (!success) errorCode = result.refusalReasonCode;
    }
  } catch (err) {
    errorCode = err.code || 'exception';
  }

  // 2. Report outcome (fire-and-forget, non-blocking)
  client.reportOutcome({
    ruleId: `${order.country.toLowerCase()}_${order.currency.toLowerCase()}_card`,
    processorId: decision.processorId,
    success,
    latencyMs: Date.now() - start,
    errorCode,
  });

  // 3. If primary failed, try fallback
  if (!success && decision.fallbackProcessorId) {
    return processWithFallback(order, decision.fallbackProcessorId);
  }

  return { success, processor: decision.processorId };
}

Express Middleware

const express = require('express');
const app = express();

app.post('/api/pay', express.json(), async (req, res) => {
  const decision = await client.getRoute({
    country: req.body.country,
    currency: req.body.currency,
    amount: req.body.amount,
  });

  const result = await executePayment(decision.processorId, req.body);

  client.reportOutcome({
    ruleId: decision.ruleId,
    processorId: decision.processorId,
    success: result.success,
    latencyMs: result.latencyMs,
  });

  res.json(result);
});

// Flush on shutdown
process.on('SIGTERM', async () => {
  await client.shutdown();
  process.exit(0);
});

Python

Install

pip install pulseroute

Initialize

from src.sdk.client import PulseRouteClient

client = PulseRouteClient(
    base_url="http://localhost:8080",
    api_key="your-api-key",
)
client.start()  # Start background outcome flushing

Stripe + Adyen Example

import stripe
import time

stripe.api_key = os.environ["STRIPE_SECRET_KEY"]

def process_payment(order: dict) -> dict:
    # 1. Ask PulseRoute where to route
    decision = client.get_route(
        country=order["country"],
        currency=order["currency"],
        payment_method="card",
        amount=order["amount"],
    )

    start = time.time()
    success = False
    error_code = None

    try:
        if decision.processor_id == "stripe":
            intent = stripe.PaymentIntent.create(
                amount=int(order["amount"] * 100),
                currency=order["currency"].lower(),
                payment_method=order["payment_method_id"],
                confirm=True,
            )
            success = intent.status == "succeeded"
            if not success:
                error_code = intent.last_payment_error.code if intent.last_payment_error else None

        elif decision.processor_id == "adyen":
            result = adyen_checkout.payments({
                "amount": {"value": int(order["amount"] * 100), "currency": order["currency"]},
                "reference": order["id"],
                "paymentMethod": order["adyen_payment_data"],
                "merchantAccount": os.environ["ADYEN_MERCHANT"],
            })
            success = result.message["resultCode"] == "Authorised"
            if not success:
                error_code = result.message.get("refusalReasonCode")

    except Exception as e:
        error_code = str(e)

    # 2. Report outcome (fire-and-forget)
    latency_ms = (time.time() - start) * 1000
    client.report_outcome_direct(
        rule_id=f"{order['country'].lower()}_{order['currency'].lower()}_card",
        processor_id=decision.processor_id,
        success=success,
        latency_ms=latency_ms,
        error_code=error_code,
    )

    # 3. Try fallback if primary failed
    if not success and decision.fallback_processor_id:
        return process_with_fallback(order, decision.fallback_processor_id)

    return {"success": success, "processor": decision.processor_id}

Django / FastAPI

# FastAPI example
from fastapi import FastAPI

app = FastAPI()

@app.on_event("startup")
def startup():
    client.start()

@app.on_event("shutdown")
def shutdown():
    client.stop()

@app.post("/api/pay")
async def pay(request: PaymentRequest):
    decision = client.get_route(
        country=request.country,
        currency=request.currency,
        amount=request.amount,
    )
    result = await execute_payment(decision.processor_id, request)

    client.report_outcome_direct(
        rule_id=f"{request.country.lower()}_{request.currency.lower()}_card",
        processor_id=decision.processor_id,
        success=result.success,
        latency_ms=result.latency_ms,
    )
    return result

Java

PulseRoute doesn't have a Java SDK yet — use the REST API directly with any HTTP client.

OkHttp Example

import okhttp3.*;
import com.google.gson.Gson;
import java.util.Map;

public class PulseRouteClient {
    private final OkHttpClient http = new OkHttpClient();
    private final Gson gson = new Gson();
    private final String baseUrl;
    private final String apiKey;

    public PulseRouteClient(String baseUrl, String apiKey) {
        this.baseUrl = baseUrl;
        this.apiKey = apiKey;
    }

    public Map<String, Object> getRoute(String country, String currency, double amount) throws Exception {
        String body = gson.toJson(Map.of(
            "country", country,
            "currency", currency,
            "payment_method", "card",
            "amount", amount
        ));

        Request req = new Request.Builder()
            .url(baseUrl + "/v1/route")
            .addHeader("X-API-Key", apiKey)
            .addHeader("Content-Type", "application/json")
            .post(RequestBody.create(body, MediaType.parse("application/json")))
            .build();

        try (Response resp = http.newCall(req).execute()) {
            return gson.fromJson(resp.body().string(), Map.class);
        }
    }

    public void reportOutcome(String ruleId, String processorId, boolean success, double latencyMs) {
        String body = gson.toJson(Map.of(
            "rule_id", ruleId,
            "processor_id", processorId,
            "success", success,
            "latency_ms", latencyMs
        ));

        Request req = new Request.Builder()
            .url(baseUrl + "/v1/outcome")
            .addHeader("X-API-Key", apiKey)
            .addHeader("Content-Type", "application/json")
            .post(RequestBody.create(body, MediaType.parse("application/json")))
            .build();

        // Fire-and-forget
        http.newCall(req).enqueue(new Callback() {
            @Override public void onFailure(Call call, java.io.IOException e) { }
            @Override public void onResponse(Call call, Response response) { response.close(); }
        });
    }
}

Spring Boot Integration

@Service
public class PaymentService {
    private final PulseRouteClient pulseRoute;

    public PaymentService() {
        this.pulseRoute = new PulseRouteClient(
            System.getenv("PULSEROUTE_URL"),
            System.getenv("PULSEROUTE_API_KEY")
        );
    }

    public PaymentResult processPayment(PaymentRequest request) {
        // 1. Get routing decision
        Map<String, Object> decision = pulseRoute.getRoute(
            request.getCountry(),
            request.getCurrency(),
            request.getAmount()
        );
        String processorId = (String) decision.get("processor_id");

        // 2. Execute payment with recommended processor
        long start = System.currentTimeMillis();
        PaymentResult result = executePayment(processorId, request);
        long latencyMs = System.currentTimeMillis() - start;

        // 3. Report outcome
        String ruleId = request.getCountry().toLowerCase() + "_"
            + request.getCurrency().toLowerCase() + "_card";
        pulseRoute.reportOutcome(ruleId, processorId, result.isSuccess(), latencyMs);

        return result;
    }
}

REST API

For any language, you only need two HTTP calls:

1. Get Routing Decision (before payment)

curl -X POST http://localhost:8080/v1/route \
  -H "Content-Type: application/json" \
  -H "X-API-Key: your-api-key" \
  -d '{
    "country": "US",
    "currency": "USD",
    "payment_method": "card",
    "amount": 99.99
  }'

Response:

{
  "processor_id": "stripe",
  "weight": 0.9,
  "fallback_processor_id": "adyen",
  "decision_source": "rules",
  "failure_probability": null
}

2. Report Outcome (after payment)

curl -X POST http://localhost:8080/v1/outcome \
  -H "Content-Type: application/json" \
  -H "X-API-Key: your-api-key" \
  -d '{
    "rule_id": "us_usd_card",
    "processor_id": "stripe",
    "success": true,
    "latency_ms": 120.0
  }'

That's it. Two calls per transaction. PulseRoute handles everything else — health monitoring, failure prediction, automatic failover, and traffic rebalancing.


Onboarding Checklist

  • [ ] Set up processors — Use the dashboard wizard or call POST /v1/onboard
  • [ ] Add SDK to your payment servicenpm install @pulseroute/sdk or pip install pulseroute
  • [ ] Call getRoute() before each payment — Get the recommended processor
  • [ ] Call reportOutcome() after each payment — Report success/failure and latency
  • [ ] Handle fallbacks — If primary fails, retry with fallbackProcessorId
  • [ ] Monitor — Check the dashboard or GET /v1/health for real-time processor health
  • [ ] Go live — Disable shadow mode when you're confident in the routing decisions

Common Patterns

Fallback on Primary Failure

async function payWithFallback(order) {
  const decision = await client.getRoute({ country: order.country, currency: order.currency, amount: order.amount });

  let result = await tryProcessor(decision.processorId, order);

  if (!result.success && decision.fallbackProcessorId) {
    // Report the failure
    client.reportOutcome({ ruleId: order.ruleId, processorId: decision.processorId, success: false, latencyMs: result.latencyMs });
    // Retry with fallback
    result = await tryProcessor(decision.fallbackProcessorId, order);
    client.reportOutcome({ ruleId: order.ruleId, processorId: decision.fallbackProcessorId, success: result.success, latencyMs: result.latencyMs });
  } else {
    client.reportOutcome({ ruleId: order.ruleId, processorId: decision.processorId, success: result.success, latencyMs: result.latencyMs });
  }

  return result;
}

Idempotent Retries

// PulseRoute decisions are deterministic for the same input within an evaluation window.
// Safe to retry the same transaction — you'll get the same processor recommendation
// unless health has changed.
const decision1 = await client.getRoute({ country: 'US', currency: 'USD' });
const decision2 = await client.getRoute({ country: 'US', currency: 'USD' });
// decision1.processorId === decision2.processorId (usually)

Graceful Degradation

try {
  const decision = await client.getRoute({ country: 'US', currency: 'USD' });
  return decision.processorId;
} catch {
  // PulseRoute is down — fall back to your default processor
  return 'stripe';
}

The SDK handles this automatically with local caching. If the API is unreachable, it returns the last known good decision.