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¶
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¶
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 service —
npm install @pulseroute/sdkorpip 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/healthfor 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.