Partner integration guide

SmartRemit provides the orchestration layer for cross-border remittance: the WhatsApp conversation, quoting, compliance screening, customer KYC flows, and a hosted pay page — under your brand. You remain the licensed money transmitter: funds never touch SmartRemit. We send your rail a signed settlement instruction; your rail reports lifecycle status back via a signed webhook.

1 · The Partner API

Base URL https://smartremit.ai/api/partner/v1. Authenticate every request with your API key (issued in the dashboard, shown once):

Authorization: Bearer <your-api-key>

Rate limit: 120 requests/minute per partner (429 + Retry-After beyond it). Errors are JSON: { "error": "…" }.

GET/corridorsYour enabled send corridors + brand
POST/quotePrice a transfer (amount_source, source_currency)
POST/beneficiaries/validateValidate payout fields for a country
POST/beneficiariesStore a beneficiary (payout details encrypted at rest)
POST/transactionsMint a transfer — Idempotency-Key header REQUIRED
GET/transactionsList your transfers (keyset: ?limit=&cursor=)
GET/transactions/:idFetch one transfer (404 outside your scope)
POST/transactions/:id/confirmConfirm funds captured → settlement begins
PUT/ratesPush one corridor's wholesale conversion rate
GET/ratesYour current rate sheet (freshness + margin)
# Mint a transfer (idempotent — safe to retry with the same key)
curl -X POST $BASE/transactions \
  -H "Authorization: Bearer $KEY" \
  -H "Idempotency-Key: order-8841" \
  -H "Content-Type: application/json" \
  -d '{
    "amount_source": 200,
    "source_currency": "USD",
    "sender":      { "phone": "15551230000", "name": "Maria Lopez", "kyc_status": "verified" },
    "beneficiary": { "name": "Anita Sharma", "phone": "919876543210",
                     "payout_method": "bank", "payout_destination": "123456789012|HDFC0001234" }
  }'

Compliance screening (sanctions) runs on every mint regardless of KYC mode — a watchlist hit returns 422 and the attempt is recorded as blocked.

2 · Rates (compete for routed flow)

Push the wholesale conversion rate you offer per corridor with PUT /rates. When your fresh rate beats the platform mid-market rate (and your settlement rail is configured), SmartRemit routes eligible platform transfers to you for settlement. Pushing a rate does not change the pricing of your own /quote or /transactions — those stay at platform mid-market.

PUT /rates — request fields
One corridor per call. Re-push before expiry to stay fresh.
FieldTypeNotes
source_currencystringRequired. ISO 4217 send currency (e.g. USD).
destination_currencystringRequired. ISO 4217 payout currency (e.g. INR); must differ from source.
effective_ratenumberRequired. Destination units per 1 source unit; 0 < rate < 100000.
ttl_secondsnumberOptional. Freshness window — default 3600, clamped to [60, 86400]. An expired rate stops competing.
# Push your USD→INR rate (fresh for 30 minutes)
curl -X PUT https://smartremit.ai/api/partner/v1/rates \
  -H "Authorization: Bearer $KEY" \
  -H "Content-Type: application/json" \
  -d '{ "source_currency": "USD", "destination_currency": "INR",
        "effective_rate": 86.4, "ttl_seconds": 1800 }'

# → 200
{ "source_currency": "USD", "destination_currency": "INR",
  "effective_rate": 86.4, "expires_at": "…", "pushed_at": "…" }

GET /rates returns your sheet: { "rates": [ { source_currency, destination_currency, effective_rate, expires_at, fresh, margin_bps } ] } fresh tells you whether the pushed rate is still competing; margin_bps is your standing platform-configured fallback margin.

3 · Settlement instructions (us → you)

When a transfer is paid (pay page or /confirm), SmartRemit POSTs a signed instruction to your configured settlement endpoint — with automatic retries and exponential backoff until your rail acks 2xx. The signature is HMAC-SHA256(rawBody, signingSecret) hex in the x-signature header.

POST <your settlementUrl>
x-signature: 3f1a…   # HMAC-SHA256 of the exact raw body

{
  "reference": "tr_abc123",          // OUR transfer id — echo it in callbacks
  "partner_id": "acme",
  "corridor": { "source": "US", "destination": "IN" },
  "payout":   { "rail": "bank", "destination": "123456789012|HDFC0001234" },
  "recipient":{ "name": "Anita Sharma", "phone": "919876543210" },
  "amount": {
    "source": 200, "currency": "USD",
    "destination": 16600, "destination_currency": "INR",
    "fx_rate": 83                     // locked at quote time
  }
}

Respond 2xx with an optional { "providerRef": "…" } — stored write-once against the transfer. Use reference to deduplicate: the instruction is at-least-once.

4 · Status webhooks (you → us)

Report lifecycle status to POST /api/payment-webhook/<provider>, signed the same way with your webhookSecret (fail-closed: unsigned or mis-signed callbacks are rejected with 401).

POST /api/payment-webhook/acme-rail
x-signature: 9c44…   # HMAC-SHA256(rawBody, webhookSecret)

{ "reference": "tr_abc123", "status": "paid_out" }
Status mapping
The state machine is forward-only — replays and out-of-order callbacks are safe.
  • created → awaiting payment (no-op transition)
  • funded → paid (customer charged on your side)
  • paid_out → delivered — triggers the branded WhatsApp delivery notifications

No rail yet? Point your integration at the hosted reference rail (providerType: simulator) — it verifies your signatures, acks a providerRef, and calls the public webhook back ~12s later, running the exact production loop end to end.

5 · Your WhatsApp number & KYC mode

Bring your own Meta WhatsApp Business number: configure the phone-number id, access token, verify token, and app secret in the dashboard, then point Meta's webhook at your dedicated callback URL (shown on your partner page). Inbound messages on your number route to your tenant — replies, OTPs, and delivery notifications leave from your number under your brand.

KYC:run it yourself (delegated mode — you attest verification and our send-gate steps aside) or use SmartRemit's built-in tiered KYC. Sanctions screening is not delegable — it always runs on our side.