Base URL: http://localhost:3000 (local) or your deployed backend.
All request bodies are JSON (Content-Type: application/json) and are limited
to 100 kB; larger bodies are rejected with 413 PAYLOAD_TOO_LARGE.
All responses are JSON.
CORS: browsers may call the API only from origins listed in
CORS_ORIGINS. Requests from any other origin, including preflights, get
403 CORS_ORIGIN_NOT_ALLOWED. Preflight allows the Content-Type,
Authorization, Idempotency-Key, X-Request-Id and X-Admin-Key request
headers, and exposes X-Request-Id, Retry-After and the X-RateLimit-*
response headers. Requests with no Origin header (server-to-server, curl)
are unaffected.
Every response carries a baseline of security headers (via
helmet), including Strict-Transport-Security,
X-Content-Type-Options: nosniff, Referrer-Policy: no-referrer and a strict
Content-Security-Policy: default-src 'none'. Swagger UI under /api-docs
gets a relaxed CSP that allows its same-origin scripts, inline styles and
data: images.
Machine-readable spec: A Swagger/OpenAPI 3.0 spec is served at
GET /api-docs/swagger.json(raw JSON) andGET /api-docs(interactive Swagger UI).Rate limits: All endpoints are subject to per-IP rate limiting. See docs/rate-limits.md for default limits, configuration, and the 429 response shape.
Errors: Every error uses one envelope,
{ "error": { "code", "message", "details", "correlationId" } }. See Error response shape.
Some endpoints are marked as deprecated and will be removed in a future version. Deprecated endpoints receive standard HTTP deprecation headers:
Deprecation: true— indicates the endpoint is deprecatedSunset: <HTTP-date>— the date when the endpoint will be permanently removedLink: <new-url>; rel="successor-version"— the replacement endpoint to migrate to
Legacy routes in backend/src/legacy_routes.rs and backend/src/routes_auth_legacy.rs carry these headers. Plan to migrate to the canonical routes before the sunset date.
Current legacy routes (to be removed):
- Old merchant endpoints under
/api/v1/merchant/*— migrate to/api/v2/merchant/* - Old settlement endpoints under
/api/v1/settlement/*— migrate to/api/v2/settlement/* - Old dispute endpoints under
/api/v1/dispute/*— migrate to/api/v2/dispute/* - Old signer endpoints under
/api/v1/signer/*— migrate to/api/v2/signer/* - Old admin endpoints under
/api/v1/admin/*— migrate to/api/v2/admin/*
Usage of deprecated endpoints is logged server-side; if you hit one, update your client to use the replacement endpoint.
Returns service health status.
Response 200
{ "status": "ok" }Checks Soroban RPC reachability and current ledger.
Response 200
{
"rpc": "reachable",
"network": "mainnet",
"ledger": 54321678
}| Status | Description |
|---|---|
503 |
Soroban RPC unreachable |
500 |
Unexpected server error |
Fetch the on-chain status of an invoice by its numeric ID.
| Parameter | Type | Description |
|---|---|---|
id |
string | Positive integer string |
Response 200
{
"invoice_id": "42",
"status": "Pending"
}| Status | Description |
|---|---|
400 |
id is not a positive integer |
404 |
Invoice not found on-chain |
503 |
Missing required environment variables |
500 |
Unexpected server error |
Cancel an invoice using the configured Soroban signer. Cancelling a Pending
invoice changes it to Cancelled; cancelling a Paid invoice starts the
refund flow and changes it to RefundRequested.
Response 200
{ "invoice_id": "42", "status": "RefundRequested", "tx_hash": "..." }| Status | Description |
|---|---|
400 |
Invalid invoice ID |
403 |
Caller is not authorized to cancel the invoice |
404 |
Invoice not found |
409 |
Invoice state does not allow cancellation |
503 |
Missing required environment variables |
Request a refund for a Paid invoice using the configured Soroban signer.
The response reports the updated RefundRequested status.
Response 200
{ "invoice_id": "42", "status": "RefundRequested", "tx_hash": "..." }| Status | Description |
|---|---|
400 |
Invalid invoice ID |
403 |
Caller is not the invoice customer |
404 |
Invoice not found |
409 |
Invoice is not Paid or a refund is already pending |
503 |
Missing required environment variables |
Lists invoices, newest first. Cursor pagination is the default. Pass the
returned next_cursor to fetch the next page; it is null when no more
invoices are available.
| Parameter | Type | Description |
|---|---|---|
cursor |
string | Opaque cursor from the previous response |
limit |
integer | Page size, 1–100 (default 20) |
status |
string | Optional invoice status filter |
merchant |
string | Optional merchant address filter |
page |
integer | Deprecated; use cursor, supported one release |
offset |
integer | Deprecated; use cursor, supported one release |
Cursor response 200
{
"data": [],
"limit": 20,
"next_cursor": "eyJjcmVhdGVkQXQiOjE3MDAwMDAwMDAwMDAsImludm9pY2VJZCI6IjQyIn0"
}When page or offset is supplied without a cursor, the legacy response
metadata (total, page, limit, totalPages, and offset) remains
available during the deprecation period. Legacy parameters cannot be combined
with a cursor.
Create a new invoice by submitting create_invoice to the Soroban RPC.
{
"merchant_address": "G...",
"token": "USDC",
"amount": 1000000,
"due_date": 1720000000
}| Field | Type | Description |
|---|---|---|
merchant_address |
string | Valid Stellar public key (G…) |
token |
string | Token identifier |
amount |
number | Positive number (in stroops / smallest unit) |
due_date |
number | Future Unix timestamp (seconds) for the due date |
Response 201
{
"invoice_id": "1",
"status": "Pending"
}| Status | Description |
|---|---|
400 |
Validation error — see error.details for field-level detail |
422 |
Soroban simulation or transaction failure |
503 |
Missing required environment variables |
504 |
Transaction confirmation timeout |
500 |
Unexpected server error |
Downloads invoices as a CSV file for accounting tools. Accepts the same filters
as GET /invoices (without pagination) and the same authentication; every
matching invoice is exported, newest first. Rows are streamed from the database
cursor, so large exports do not load into memory.
| Parameter | Type | Description |
|---|---|---|
status |
string | Optional. Pending, Paid, Expired, Cancelled, RefundRequested, Released |
merchant |
string | Optional. Merchant Stellar address |
Response 200 — Content-Type: text/csv; charset=utf-8,
Content-Disposition: attachment; filename="invoices-2026-09-25.csv"
(invoices-<status>-<date>.csv when filtered by status).
invoice_id,merchant_address,token,amount_raw,amount,status,reference,due_date,created_at,updated_at
1,GDR7...T5XT,USDC,12500000,1.25 USDC,Paid,"Order ""A"", batch 2",2026-01-01T00:00:00.000Z,2025-12-01T10:00:00.000Z,2025-12-02T10:00:00.000Z- Fields follow RFC 4180: values containing commas, quotes or line breaks are quoted, and quotes are doubled. Rows end with CRLF.
- Text beginning with
=,+,-,@, tab or CR is prefixed with'so spreadsheets do not evaluate it as a formula. amount_rawis in the token's smallest unit;amountis the human-readable value with the token symbol. Tokens default to 7 decimals with the stored token value as symbol; override per token with theTOKEN_METADATAenvironment variable, e.g.{"C...USDC_CONTRACT":{"symbol":"USDC","decimals":7}}.- Dates are ISO 8601 in UTC.
| Status | Description |
|---|---|
500 |
Database error before streaming started (JSON body). Errors after streaming started abort the download |
Raise a dispute linked to a settlement, transitioning it to OnHold.
{
"claimant_address": "G...",
"settlement_id": "5",
"reason": "Goods not delivered"
}| Field | Type | Required | Description |
|---|---|---|---|
claimant_address |
string | Yes | Valid Stellar public key of the disputing party |
settlement_id |
string | Yes | Positive integer string identifying settlement |
reason |
string | No | Human-readable reason for the dispute |
Response 201
{
"dispute_id": "5-1720000000000",
"settlement_id": "5",
"claimant_address": "G...",
"status": "Raised",
"settlement_status": "OnHold"
}| Status | Description |
|---|---|
400 |
Validation error — see error.details for field-level detail |
503 |
Missing required environment variables |
500 |
Unexpected server error |
List disputes with their current vote tallies, newest first.
| Parameter | Type | Default | Description |
|---|---|---|---|
status |
string | — | Raised (open) or Resolved |
settlement_id |
string | — | Only disputes for this settlement |
page |
integer | 1 |
1-based page number |
limit |
integer | 20 |
Page size, 1–100 |
Send x-admin-key to include voter identities (see
Voter visibility).
Response 200
{
"data": [
{
"dispute_id": "5-1720000000000",
"settlement_id": "5",
"claimant_address": "G...",
"reason": "Goods not delivered",
"status": "Raised",
"outcome": null,
"claimant_weight": 1,
"counterparty_weight": 0,
"resolution_weight": 1,
"threshold": 2,
"vote_count": 1,
"created_at": "2026-09-25T10:00:00.000Z",
"resolved_at": null
}
],
"total": 1,
"page": 1,
"limit": 20,
"totalPages": 1
}| Status | Description |
|---|---|
400 |
Invalid query parameter — see error.details |
401 |
x-admin-key supplied but invalid |
Full details for one dispute: the same fields as a list item. For admins it
also includes votes:
{
"dispute_id": "5-1720000000000",
"status": "Resolved",
"outcome": "ResolvedClaimant",
"claimant_weight": 2,
"counterparty_weight": 1,
"resolution_weight": 3,
"threshold": 2,
"vote_count": 3,
"resolved_at": "2026-09-25T10:05:00.000Z",
"votes": [
{ "signer": "G...", "vote": "ResolvedClaimant", "weight": 1, "voted_at": "2026-09-25T10:01:00.000Z" }
]
}| Status | Description |
|---|---|
401 |
x-admin-key supplied but invalid |
404 |
No dispute with this ID |
Cast a weighted vote (ResolvedClaimant or ResolvedCounterparty). The
dispute resolves as soon as either side's weight reaches the threshold
(DISPUTE_VOTE_THRESHOLD, default 2).
| Status | Description |
|---|---|
400 |
Validation error |
404 |
No dispute with this ID |
409 |
Signer already voted, or dispute already resolved (details.outcome) |
Tallies (claimant_weight, counterparty_weight, resolution_weight,
vote_count, outcome) are public. Who voted which way is visible only to
admins (a valid x-admin-key header), via the votes array.
Signers are the treasury's multi-sig keys. Publishing each key's vote in a public API makes it easy to single out and pressure individual signers, and clients showing dispute progress only need the tallies. The API hides voter identities as a precaution: on-chain votes are still public on the ledger, so this is not a confidentiality guarantee.
Returns all settlements with Pending status from the indexed database.
Response 200
[
{
"id": 1,
"merchant_address": "G...",
"amount": "5000000",
"approvals": ["G..."],
"approval_weight": 1,
"status": "Pending",
"hold_reason": null
}
]| Status | Description |
|---|---|
500 |
Database error |
Approve a pending settlement by submitting approve_settlement to the treasury contract.
{ "settlement_id": 1 }| Field | Type | Description |
|---|---|---|
settlement_id |
number | Positive integer settlement ID |
Response 200
{
"id": 1,
"merchant_address": "G...",
"amount": "5000000",
"approvals": ["G..."],
"approval_weight": 2,
"status": "Pending",
"hold_reason": null,
"tx_hash": "abc123..."
}| Status | Description |
|---|---|
400 |
settlement_id is not a positive integer |
503 |
Missing required environment variables |
500 |
Unexpected server error |
Execute a fully-approved settlement after verifying the treasury USDC balance.
{
"settlement_id": 1,
"token_contract": "C..."
}| Field | Type | Required | Description |
|---|---|---|---|
settlement_id |
number | Yes | Positive integer settlement ID |
token_contract |
string | No | Token contract address — defaults to USDC_CONTRACT_ID env var |
Response 200
{
"tx_hash": "abc123...",
"settlement_id": 1,
"balance_checked": "10000000",
"amount_required": "5000000"
}| Status | Description |
|---|---|
400 |
settlement_id is not a positive integer |
409 |
Settlement is not in Pending status |
422 |
Insufficient treasury balance or simulation failure |
503 |
Missing required environment variables |
500 |
Unexpected server error |
Returns the current approval threshold from the treasury contract.
Response 200
{ "threshold": 2 }| Status | Description |
|---|---|
422 |
Soroban simulation failure |
503 |
Missing required environment variables |
500 |
Unexpected server error |
Update the treasury approval threshold.
{ "threshold": 3 }| Field | Type | Description |
|---|---|---|
threshold |
number | Positive integer ≥ 1 |
Response 200
{
"threshold": 3,
"tx_hash": "abc123..."
}| Status | Description |
|---|---|
400 |
threshold is not a positive integer |
422 |
Soroban simulation or transaction failure |
503 |
Missing required environment variables |
500 |
Unexpected server error |
Returns all settlements that are currently on hold. A hold is placed when a signer flags a settlement as requiring manual review before execution can proceed.
Query parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
page |
number | No | Page number (1-based, default: 1) |
limit |
number | No | Results per page (default: 20, max: 100) |
Response 200
{
"settlements": [
{
"id": 7,
"merchant_address": "G...",
"amount": "5000000",
"approvals": [],
"approval_weight": 0,
"status": "OnHold",
"hold_reason": "Merchant KYC under review"
}
]
}| Status | Description |
|---|---|
500 |
Database error |
Releases a held settlement, restoring it to Pending so the normal approval and
execution flow can resume. Calls release_hold on the treasury contract.
See also: release_hold in the Contract Interaction Guide.
{ "settlement_id": 7 }| Field | Type | Description |
|---|---|---|
settlement_id |
number | Positive integer settlement ID |
Response 200
{
"id": 7,
"merchant_address": "G...",
"amount": "5000000",
"approvals": [],
"approval_weight": 0,
"status": "Pending",
"hold_reason": null,
"tx_hash": "abc123..."
}| Status | Description |
|---|---|
400 |
settlement_id is not a positive integer |
409 |
Settlement is not currently on hold |
422 |
Soroban simulation or transaction failure |
503 |
Missing required environment variables |
500 |
Unexpected server error |
Escalates a held settlement to the on-chain dispute-resolution flow.
Calls raise_dispute on the treasury contract and begins a multi-sig governance
vote among the configured signers.
See also: raise_dispute in the Contract Interaction Guide.
{
"settlement_id": 7,
"reason": "Merchant disputes the invoice amount"
}| Field | Type | Required | Description |
|---|---|---|---|
settlement_id |
number | Yes | Positive integer settlement ID |
reason |
string | No | Human-readable reason for escalation (max 512 chars) |
Response 200
{
"dispute_id": "7-1720000001000",
"settlement_id": "7",
"status": "Raised",
"settlement_status": "OnHold",
"tx_hash": "abc123..."
}| Status | Description |
|---|---|
400 |
settlement_id is not a positive integer |
422 |
Soroban simulation or transaction failure |
503 |
Missing required environment variables |
500 |
Unexpected server error |
Returns the current invoice grace window in seconds.
Response 200
{ "grace_window_seconds": 86400 }| Status | Description |
|---|---|
422 |
Soroban simulation failure |
503 |
Missing required environment variables |
500 |
Unexpected server error |
Update the invoice grace window.
{ "grace_window_seconds": 172800 }| Field | Type | Description |
|---|---|---|
grace_window_seconds |
number | Positive integer number of seconds |
Response 200
{
"grace_window_seconds": 172800,
"tx_hash": "abc123..."
}| Status | Description |
|---|---|
400 |
grace_window_seconds is not a positive integer |
422 |
Soroban simulation or transaction failure |
503 |
Missing required environment variables |
500 |
Unexpected server error |
Returns the paginated, consolidated audit trail emitted by the compliance contract.
The service indexes address_allowed, address_allowed_until, address_blocked, and
address_cleared events in MongoDB.
| Parameter | Type | Required | Description |
|---|---|---|---|
address |
string | No | Stellar public key filter |
event_type |
string | No | One of the four compliance event types |
from_ledger |
integer | No | Inclusive lower ledger bound |
to_ledger |
integer | No | Inclusive upper ledger bound |
page |
integer | No | 1-based page, default 1 |
limit |
integer | No | Page size, default 20, maximum 100 |
Response 200
{
"events": [
{
"event_id": "paging-token",
"event_type": "address_cleared",
"address": "G...",
"expires_at": null,
"ledger": 123,
"ledger_closed_at": "2026-08-27T12:00:00.000Z",
"transaction_hash": "abc123...",
"contract_id": "C...",
"paging_token": "paging-token",
"created_at": "2026-08-27T12:00:01.000Z"
}
],
"page": 1,
"limit": 20,
"total": 1,
"has_more": false
}| Status | Description |
|---|---|
400 |
Invalid query parameter |
503 |
MongoDB unavailable |
500 |
Unexpected server error |
Returns protocol totals for the admin dashboard. With bucket, returns a time
series instead, for charts such as invoices per day or settlement volume per
week.
| Parameter | Type | Description |
|---|---|---|
start_date |
number | Optional. Unix timestamp (seconds), inclusive |
end_date |
number | Optional. Unix timestamp (seconds), inclusive. Defaults to now |
bucket |
string | Optional. day, week or month |
merchant |
string | Optional. Only count invoices of this merchant (series only) |
token |
string | Optional. Only count invoices in this token (series only) |
Time zone: buckets are always computed in UTC. Weeks start on Monday
(ISO 8601) and months on the 1st. period is the first day of the bucket in
YYYY-MM-DD form. Without start_date the series covers the last 30 days,
12 weeks or 12 months, depending on bucket. A request may span at most 1000
buckets.
Every bucket in the range is returned, with zeros for periods without
activity, so charts do not skip dates. count is the number of invoices
created in the bucket; volume is the sum of raw amounts (smallest token
unit) of those invoices that are settled (Paid or Released).
Response 200 (with bucket=day)
{
"bucket": "day",
"timezone": "UTC",
"start_date": 1767225600,
"end_date": 1767398400,
"series": [
{ "period": "2026-01-01", "count": 4, "volume": 3000000 },
{ "period": "2026-01-02", "count": 0, "volume": 0 },
{ "period": "2026-01-03", "count": 1, "volume": 0 }
]
}| Status | Description |
|---|---|
400 |
Invalid bucket, timestamps, merchant, or a range over 1000 buckets |
500 |
Unexpected server error |
COMEBACKHERE signs every outbound webhook POST with HMAC-SHA256 so your endpoint can verify payload authenticity before processing it.
For the full payload reference, retry schedule, idempotency guidance, and language-specific verification examples, see Webhook Payload Reference. Security note: this is a security-sensitive feature. Treat your signing secret with the same care as a private key. Rotate it immediately if it is ever exposed.
| Header | Value |
|---|---|
X-COMEBACKHERE-Signature |
Lowercase hex-encoded HMAC-SHA256 digest |
The digest is computed over the raw JSON request body (exactly as sent over
the wire) using the WEBHOOK_SIGNING_SECRET environment variable as the key.
import { createHmac, timingSafeEqual } from "crypto"
function verifyWebhook(
rawBody: string, // The unparsed request body string
signature: string, // Value of X-COMEBACKHERE-Signature header
secret: string, // Your WEBHOOK_SIGNING_SECRET
): boolean {
const expected = createHmac("sha256", secret).update(rawBody, "utf8").digest("hex")
const expectedBuf = Buffer.from(expected, "hex")
const actualBuf = Buffer.from(signature, "hex")
if (expectedBuf.length !== actualBuf.length) return false
return timingSafeEqual(expectedBuf, actualBuf)
}Always use a constant-time comparison (e.g. crypto.timingSafeEqual) when
comparing signatures to prevent timing side-channel attacks.
Failed webhook deliveries are retained in MongoDB's webhook_dead_letters
collection with the target URL, original payload, final error, and attempt
history. These operator endpoints require x-admin-key:
| Method and path | Description |
|---|---|
GET /webhooks/dead-letters |
List the latest 100 permanently failed deliveries |
POST /webhooks/dead-letters/:id/replay |
Retry one delivery by its idempotency key; remove the dead letter only on success |
All events share a common event field plus event-specific fields:
{
"event": "settlement_executed",
"settlement_id": 1,
"tx_hash": "abc123..."
}| Event | Extra fields |
|---|---|
settlement_proposed |
settlement_id, merchant_address, amount, token, tx_hash |
settlement_approved |
settlement_id, signer, approval_weight, tx_hash |
settlement_executed |
settlement_id, tx_hash |
| Variable | Description |
|---|---|
WEBHOOK_URL |
Merchant endpoint that receives webhook POSTs |
WEBHOOK_SIGNING_SECRET |
HMAC-SHA256 signing secret (minimum 32 characters recommended) |
Set both variables in your deployment environment. If WEBHOOK_URL is not set,
webhook delivery is skipped silently (no error).
Fetch aggregated protocol metrics and performance data.
Query parameters (all optional)
| Parameter | Type | Description |
|---|---|---|
start_date |
string | ISO-8601 date (e.g. 2025-03-01) — default: 30 days ago |
end_date |
string | ISO-8601 date (e.g. 2025-03-31) — default: today |
resolution |
string | Aggregation granularity: daily, weekly, monthly (default: daily) |
Response 200
{
"period": {
"start": "2025-03-01T00:00:00Z",
"end": "2025-03-31T23:59:59Z"
},
"summary": {
"total_invoices": 156,
"total_revenue_usdc": "150000000000",
"avg_invoice_amount_usdc": "961538462",
"settlement_success_rate": 0.98
},
"by_date": [
{
"date": "2025-03-01",
"invoices_created": 5,
"invoices_paid": 4,
"revenue_usdc": "4800000000",
"disputes_raised": 0
}
]
}| Field | Type | Description |
|---|---|---|
period |
object | Query date range (ISO-8601) |
summary |
object | Aggregate metrics across the entire period |
by_date |
array | Per-day breakdown (if resolution is daily) |
total_invoices |
number | Count of all invoices in the period |
total_revenue_usdc |
string | Sum of all paid invoice amounts (stroops) |
avg_invoice_amount_usdc |
string | Mean invoice amount (stroops) |
settlement_success_rate |
number | Fraction of settlements executed successfully (0–1) |
| Status | Description |
|---|---|
400 |
Invalid date format or date range |
503 |
Database connection error |
500 |
Unexpected server error |
Fetch dispute status for a settlement.
| Parameter | Type | Description |
|---|---|---|
settlementId |
string | Settlement numeric ID as string |
Response 200
{
"settlement_id": 15,
"dispute_status": "Raised",
"claimant": "G...",
"reason": "Payment never received",
"created_at": "2025-03-15T10:30:00Z",
"resolution_weight": 0,
"threshold": 100
}| Field | Type | Description |
|---|---|---|
settlement_id |
number | Settlement ID |
dispute_status |
string | One of: Raised, ResolvedClaimant, ResolvedCounterparty, None |
claimant |
string | Stellar address that raised the dispute |
reason |
string | Text reason provided by the claimant |
created_at |
string | ISO-8601 timestamp when dispute was raised |
resolution_weight |
number | Cumulative signer weight voting on the dispute |
threshold |
number | Signer weight threshold required to resolve |
| Status | Description |
|---|---|
400 |
settlementId is not a positive integer |
404 |
No dispute found for this settlement |
503 |
Missing required environment variables |
500 |
Unexpected server error |
Vote on a dispute resolution (admin/signer only).
Request body
{
"vote": "ClaimantWins",
"admin_key": "secret_key_123"
}| Field | Type | Description |
|---|---|---|
vote |
string | Vote direction: ClaimantWins or CounterpartyWins |
admin_key |
string | Admin secret key (if using key-based authorization) |
Response 200
{
"settlement_id": 15,
"resolution_weight": 65,
"threshold": 100,
"outcome": "pending"
}| Status | Description |
|---|---|
400 |
Invalid vote direction or settlement ID |
401 |
Missing or invalid admin_key |
404 |
No dispute found |
409 |
Dispute already resolved or settlement not held |
503 |
Missing required environment variables or contract unavailable |
500 |
Unexpected server error |
Every non-2xx response, from every endpoint, uses the same envelope:
{
"error": {
"code": "VALIDATION_ERROR",
"message": "settlement_id: Must be a positive integer",
"details": [{ "field": "settlement_id", "message": "Must be a positive integer" }],
"correlationId": "5f1c9a8e-2b7d-4c1e-9a3f-0d2e6b7c8a91"
}
}| Field | Type | Description |
|---|---|---|
code |
string | Stable, machine-readable error code (see below). Branch on this, not on message. |
message |
string | Human-readable description. May change between releases. |
details |
any | null | Extra structured context; shape depends on code. null when there is nothing to add. |
correlationId |
string | null | Same value as the X-Request-Id response header. Quote it when contacting support. |
Clients may send their own X-Request-Id header; it is echoed back as both the
header and correlationId. Otherwise the server generates a UUID v4.
| HTTP | code |
When | details |
|---|---|---|---|
| 400 | VALIDATION_ERROR |
Body, path or query parameters failed schema validation | [{ field, message }], one per issue |
| 400 | INVALID_JSON |
Request body is not valid JSON | null |
| 401 | UNAUTHORIZED |
Missing or invalid x-admin-key |
null |
| 403 | FORBIDDEN |
Caller lacks permission | null |
| 403 | CORS_ORIGIN_NOT_ALLOWED |
Browser Origin is not in CORS_ORIGINS |
{ origin } |
| 404 | NOT_FOUND |
Resource or route does not exist | null |
| 409 | CONFLICT |
Request conflicts with current state (e.g. dispute already resolved) | Endpoint-specific, e.g. { outcome } |
| 413 | PAYLOAD_TOO_LARGE |
JSON body exceeds 100 kB | { limitBytes } |
| 4xx/5xx | CONTRACT_ERROR |
A Soroban contract returned Error(Contract, #N) |
{ contractCode: N } — see error-codes.md |
| 422 | UNPROCESSABLE_ENTITY |
Soroban simulation / submission failed without a contract code | null |
| 429 | RATE_LIMITED |
Per-IP rate limit exceeded | { retryAfter } (seconds) |
| 500 | INTERNAL_ERROR |
Unexpected server error | null |
| 503 | SERVICE_MISCONFIGURED |
Required environment variables are missing | null |
| 503 | SERVICE_UNAVAILABLE |
A dependency (e.g. MongoDB) is unreachable | null |
| 504 | GATEWAY_TIMEOUT |
Timed out waiting for Soroban transaction confirmation | null |
Routes do not build error responses by hand. They throw a typed error from
comebackhere-backend/src/lib/errors.ts (ValidationError, NotFoundError,
ConflictError, UnauthorizedError, ContractError, …) and the central
handler in src/middleware/errorHandler.ts renders the envelope. Async
handlers are wrapped in asyncHandler so rejected promises reach it.
| Variable | Description |
|---|---|
SOROBAN_RPC_URL |
Soroban RPC endpoint (e.g. http://localhost:8000/soroban/rpc) |
INVOICE_CONTRACT_ID |
Deployed invoice contract address |
TREASURY_CONTRACT_ID |
Deployed treasury contract address |
USDC_CONTRACT_ID |
USDC token contract address |
SETTLEMENT_CONTRACT_ID |
Settlement contract address (disputes) |
SIGNER_SECRET_KEY |
Stellar secret key for signing transactions |
NETWORK_PASSPHRASE |
Stellar network passphrase |
WEBHOOK_URL |
Merchant webhook endpoint URL |
WEBHOOK_SIGNING_SECRET |
HMAC-SHA256 signing secret for outbound webhooks |
WEBHOOK_MAX_ATTEMPTS |
Maximum webhook attempts (default 5) |
WEBHOOK_BASE_DELAY_MS |
Initial retry delay in ms (default 1000) |
WEBHOOK_MAX_DELAY_MS |
Maximum backoff delay in ms (default 60000) |
WEBHOOK_JITTER_RATIO |
Retry jitter from 0 to 1 (default 0.2) |
PORT |
HTTP server port (default 3000) |
CORS_ORIGINS |
Comma-separated allowlist of browser origins, e.g. http://localhost:5173,https://app.example.com. Bare origins only (no path, trailing slash or *); invalid entries fail startup. Unset = no cross-origin access. |