Trust Layer
How it worksCompareGuidesTrust centerFAQ
Sign inStartStart selling
For developers & partners

Integrate Trust Layer without the guesswork.

Everything we learned shipping production escrow with storefront builders: redirect URLs, embed checkout, webhooks, fulfillment sync, phone normalization, and how to record payments correctly.

Start integratingOpen API playgroundRequest API access
QuickstartAuthenticationAPI referenceRedirect URLsEmbed checkoutClient eventsServer webhooksEscrow sync

On this page

  • Quickstart
  • Authentication
  • API reference
  • Redirect URLs
  • Embed checkout
  • Client events
  • Server webhooks
  • Escrow sync
  • Buyer phone & OTP
  • Recording payments
  • Trust badge
  • Reputation API
  • Recipes
  • Troubleshooting
  • API playground
Quickstart

Protected checkout in two steps.

Mint a checkout on your server with a secret API key, then open it in the buyer's browser — embedded modal or full-page redirect. Pass redirect URLs so buyers return to your storefront after pay and release.

Step 1

Mint a checkout server-side

Call the Partner API when the buyer chooses protected payment. Persist transactionId on your order row. Never expose the API key to the browser.

server.ts
import { TrustLayerClient } from '@trust-layer/sdk';

const tl = new TrustLayerClient({
  apiKey: process.env.TRUSTLAYER_API_KEY, // server-side only — never in the browser
  baseUrl: 'https://api.trustlayer.africa',
});

const { token, checkoutUrl, transactionId } = await tl.createCheckout({
  sellerId: 'seller-uuid',
  buyerPhone: '+2348012345678', // E.164 — normalize before this call
  amountKobo: 250_000_00, // ₦250,000.00
  itemTitle: 'Order #1042',
  returnUrl: 'https://yourstore.com/orders/verify?checkoutId=abc',
  cancelUrl: 'https://yourstore.com/checkout',
  completeUrl: 'https://yourstore.com/orders/1042/thanks',
});
// → pass `token` to the browser; persist `transactionId` on your order row

Step 2

Open checkout in the browser

Load checkout.js and open the token in a modal, or redirect to checkoutUrl. Listen for trustlayer:paid and navigate to your returnUrl verify page.

checkout.html
<script src="https://trustlayer.africa/embed/checkout.js"></script>

<button
  data-trustlayer-token="THE_TOKEN"
  data-trustlayer-base="https://trustlayer.africa"
>
  Pay with protection
</button>

<script>
  TrustLayerCheckout.open({
    token: 'THE_TOKEN',
    baseUrl: 'https://trustlayer.africa', // optional — defaults to script origin
    onEvent: (e) => {
      if (e.type === 'trustlayer:paid') {
        window.location.href = '/orders/verify?checkoutId=…';
      }
    },
    onClose: () => console.log('modal closed'),
  });
</script>
Authentication

API keys & scopes

All Partner API calls authenticate with your secret key. Send it as x-api-key (recommended) or Authorization: Bearer. Keys are hashed at rest and scoped to least privilege.

ScopeUse
checkout:createMint checkouts; dispatch and deliver partner-originated escrows
reputation:readLook up seller trust profiles (metered)
seller:createProvision Trust Layer sellers for merchants on your platform

Server-side only

The API key must never ship to browsers, mobile apps, or third-party widgets. Mint checkouts from your backend; pass only the one-time payment token to the client.
API reference

Partner REST endpoints

Base URL: https://api.trustlayer.africa. Wrapped by @trust-layer/sdk.

MethodPathScopeSummary
POST/partner/sellersseller:createProvision a seller (idempotent on externalMerchantId)
POST/partner/checkoutscheckout:createMint a protected checkout; optional return/cancel/complete URLs
GET/partner/escrows/:transactionIdcheckout:createRead escrow state for a partner-originated transaction
POST/partner/escrows/:transactionId/dispatchcheckout:createMark shipped (FUNDED → IN_DELIVERY) for escrows you originated
POST/partner/escrows/:transactionId/delivercheckout:createMark delivered; opens buyer inspection window
GET/partner/reputation/:keyreputation:readRaaS lookup by seller handle or id (risk band included)
POST/partner/reputation/batchreputation:readBatch reputation lookup (1–50 keys)
GET/partner/sellers/:sellerId/reputationreputation:readDetailed score breakdown for one seller id
GET/partner/usage(key only)Originated checkout + reputation lookup counts
Redirect URLs

Three URLs for three lifecycle moments.

Pass returnUrl, cancelUrl, and completeUrl when minting a checkout. Trust Layer validates each origin against your partner allowlist before storing them on the escrow row.

returnUrl

After pay (FUNDED)

Send buyers here to sync your order — e.g. a verify-trust-payment page that polls until funded.

cancelUrl

Before pay (CREATED)

Shown while checkout is unpaid. Use your cart or delivery page so buyers can fix details and retry.

completeUrl

After release (RELEASED)

Post-settlement thank-you page. Hosted checkout auto-redirects here after buyer confirms delivery.

redirects.ts
// Three URLs — three moments in the buyer journey

returnUrl   // After pay (FUNDED): sync your order, show "payment received"
cancelUrl   // Before pay (CREATED): buyer abandoned hosted checkout
completeUrl // After release (RELEASED/REFUNDED): order fully settled

// Hosted checkout picks:
//   active checkout  → returnUrl (+ cancelUrl while CREATED)
//   settled checkout → completeUrl (falls back to returnUrl if omitted)

await tl.createCheckout({
  /* … */
  returnUrl: `${origin}/verify?checkoutId=${checkoutId}`,
  cancelUrl: `${origin}/checkout`,
  completeUrl: `${origin}/orders/${orderId}/thanks`,
});

Allowlist

Only HTTPS (or http://localhost) origins on your partner record are accepted. Paths and query strings are allowed — validation is exact origin match only. Contact partners@trustlayer.africa to register production and staging origins.

Buyer-facing labels

Hosted checkout buttons use your partner name (e.g. "Return to Acme") when redirect URLs are set — never hardcoded client names. With no redirect URLs configured, no return button is shown; always pass returnUrl, cancelUrl, and completeUrl at mint time.

Mint fresh checkouts

Redirect URLs are stored when the checkout is created. Reopening an old payment link from before you passed URLs will not have them — always mint a new checkout for each protected payment attempt.
Embed checkout

Modal iframe vs full redirect

checkout.js opens /pay/{token}?embed=1 in a modal. Use checkoutUrl for a full-page experience or when embed is blocked (CSP, in-app browsers, strict third-party cookie policies).

ApproachWhen to use
Embed (recommended)Storefronts and dashboards where buyers should stay on your domain. Listen for trustlayer:paid then navigate to returnUrl.
Full redirectFallback when embed fails, WhatsApp in-app browser, or you prefer a hosted-only flow. Trust Layer shows partner return CTAs on the checkout page.
Embed + redirect fallbackProduction pattern: try TrustLayerCheckout.open(); on script error or timeout, window.location.assign(checkoutUrl).

Options: token (required), baseUrl, onEvent, onClose. Declarative buttons use data-trustlayer-token and optional data-trustlayer-base.

Client events

postMessage events from the embed

The hosted checkout posts these to window.parent while running inside your iframe. Your onEvent callback receives { type, token, state }.

EventWhen
trustlayer:stateEvery escrow state change while the iframe is open
trustlayer:paidEscrow reached FUNDED (buyer paid into escrow)
trustlayer:releasedEscrow reached RELEASED (buyer confirmed delivery)
trustlayer:refundedEscrow reached REFUNDED
trustlayer:closeEmbedded checkout signals the host may close the modal (after FUNDED or RELEASED)

Do not rely on close alone

trustlayer:close fires on FUNDED and RELEASED in embed mode, but your verify page should still confirm payment server-side (webhook or polling). Closing the modal is not proof of funds.
Server webhooks

Async notifications to your backend

Configure webhookUrl (and webhookSecret) on your partner record. Deliveries are queued with retries; verify X-TrustLayer-Signature when a secret is set.

EventWhen
checkout.createdPartner API minted a checkout
checkout.fundedBuyer paid; funds locked in escrow
escrow.dispatchedPartner dispatch API or seller marked shipped
escrow.deliveredPartner deliver API or seller marked delivered
checkout.releasedBuyer confirmed delivery; seller wallet credited
checkout.refundedEscrow refunded to buyer (dispute or admin)
checkout.cancelledUnfunded escrow cancelled (seller, partner, or link expiry cron)
dispute.openedBuyer opened a dispute; funds frozen
dispute.resolvedAdmin resolved dispute (RELEASE or REFUND outcome in payload)
webhook.ts
import { createHmac, timingSafeEqual } from 'crypto';

function verifyTrustLayerWebhook(rawBody: string, signature: string | undefined, secret: string) {
  if (!signature) return false;
  const expected = createHmac('sha256', secret).update(rawBody).digest('hex');
  try {
    return timingSafeEqual(Buffer.from(signature), Buffer.from(expected));
  } catch {
    return false;
  }
}

// POST body shape:
// { id, event: 'checkout.funded', createdAt, data: { transactionId, amountKobo, … } }

Not webhooked today

Refunds, disputes, and cancellations do not emit partner webhooks yet. Use your returnUrl verify flow or poll checkout status for edge cases.
Escrow sync

Fulfillment from your merchant portal

When merchants manage orders in your app (not Trust Layer), call dispatch and deliver so buyers see the correct checkout stepper and inspection window opens.

StateYour action
CREATEDWait for buyer pay; show cancelUrl if they abandon
FUNDEDRecord sale locally; ship goods; call dispatch API
IN_DELIVERYIn transit; call deliver when buyer receives
INSPECTIONBuyer confirms or disputes on Trust Layer checkout
RELEASEDEscrow complete; redirect to completeUrl
REFUNDEDFunds returned to buyer; close order on your side
DISPUTEDListen for dispute.opened; pause fulfillment until dispute.resolved
REFUNDEDListen for checkout.refunded; close order and restore stock if needed
fulfillment.ts
// When your merchant marks an order shipped in YOUR portal:
await fetch(`${TRUSTLAYER_API}/partner/escrows/${transactionId}/dispatch`, {
  method: 'POST',
  headers: { 'x-api-key': TRUSTLAYER_API_KEY },
});

// When delivered (opens buyer inspection / confirm-delivery on Trust Layer):
await fetch(`${TRUSTLAYER_API}/partner/escrows/${transactionId}/deliver`, {
  method: 'POST',
  headers: { 'x-api-key': TRUSTLAYER_API_KEY },
});

// Buyer still confirms delivery on Trust Layer (OTP) unless auto-release fires.

Buyer release still happens on Trust Layer

Dispatch and deliver sync escrow state; the buyer confirms delivery (OTP) on the Trust Layer checkout link unless auto-release fires. Seller wallet credit happens at RELEASED, not at FUNDED.
Buyer phone & OTP

E.164 phone and hosted OTP login

buyerPhone must be Nigerian E.164 (+234XXXXXXXXXX). Buyers verify via SMS OTP on the hosted checkout before paying — even in embed mode.

phone.ts
// Accept 080… or +234… from your form; always send +234XXXXXXXXXX to Trust Layer
function normalizeNgPhoneE164(input: string): string | null {
  const digits = input.replace(/\D/g, '');
  if (digits.length === 11 && digits.startsWith('0')) return `+234${digits.slice(1)}`;
  if (digits.length === 13 && digits.startsWith('234')) return `+${digits}`;
  if (input.startsWith('+234') && input.length === 14) return input;
  return null;
}

OTP on every new session

If the buyer is not already logged in to Trust Layer, they enter a 6-digit code on the checkout page. Your embed onEvent may fire only after they complete OTP and pay.

Seller phone used as buyer phone

Valid for testing and small merchants. OTP succeeds; ensure your UI advances on trustlayer:paid rather than waiting for a different buyer identity.

Pilot / staging

When SMS delivery is in pilot mode, the checkout may surface the code on-screen for testing. Production partners should use real buyer numbers.
Recording payments

What to record locally, and when

Partners often mirror Trust Layer state in their own ledger. These rules prevent the most common accounting mismatches.

MomentTrust LayerYour platform
checkout.funded / trustlayer:paidFunds in escrow holdMark order paid; type as escrow/protected — not wallet-settled
Merchant shipsIN_DELIVERY → INSPECTIONUpdate fulfillment status; call dispatch + deliver APIs
checkout.releasedSeller wallet creditedOrder complete; payout is on Trust Layer — your Money page may show metadata only
Reused checkout rowSame transactionId updatedUpdate paidAt / updatedAt — stale createdAt from an earlier card attempt misleads reports
Trust badge

Embeddable seller badge

Show tier and trust score on listing pages with a signed iframe. Unsigned loads still work for public profiles but signed badges prevent tampering.

badge.html
<!-- Signed badge iframe — tier + score for a seller handle -->
<iframe
  src="https://trustlayer.africa/embed/badge/adas-thrift?t=ISSUED_AT_MS&sig=HMAC_HEX"
  width="320"
  height="88"
  style="border:0;border-radius:12px"
  loading="lazy"
  title="Trust Layer seller badge"
></iframe>

// Sign server-side with your badge secret (30-day TTL). See badge-sign in @trust-layer/shared.
Reputation API

Look up any seller from your platform.

Requires reputation:read scope. Lookups are metered. riskBand (LOW / MEDIUM / HIGH) is included for underwriting-style decisions.

reputation.ts
const rep = await tl.getReputation('oge-ventures');
// → { score: 588, tier: 'NEW',
//     riskBand: 'LOW', completedSales: 0, … }

const batch = await tl.getReputationBatch(['oge-ventures', 'seller-2']);

Risk bands

Each profile resolves to LOW, MEDIUM, or HIGH — act without interpreting raw scores.

Batch up to 50

Score a catalogue or search results page in one round trip.

Handle or id

Use public store handles where available; fall back to Trust Layer seller id.

Recipes

Integration patterns

Copy patterns that survived production traffic — storefront builders, WhatsApp bots, reputation-only.

Storefront builder

Mint checkout at cart pay → embed with redirect fallback → verify page polls until funded → merchant portal calls dispatch/deliver → buyer releases on Trust Layer → completeUrl.

WhatsApp / DM bot

Mint checkout server-side when buyer confirms → send checkoutUrl link (full redirect works best in in-app browsers) → webhook checkout.funded to notify merchant.

Reputation-only

No checkout scope needed. Batch-getReputation on listing pages; badge iframe on seller profiles. Metering applies per lookup.

Marketplace onboarding

POST /partner/sellers when a merchant signs up (idempotent on your merchant id). Store returned sellerId for checkout minting.

storefront-recipe.ts
// Storefront pattern (production-tested):
// 1. Mint checkout server-side with all three redirect URLs
// 2. Try TrustLayerCheckout.open({ token, onEvent }) first
// 3. On trustlayer:paid → navigate to returnUrl (verify page)
// 4. Verify page polls YOUR backend until trustLayerFunded === true
// 5. On merchant ship/fulfill → call partner dispatch + deliver APIs
// 6. Buyer releases on Trust Layer → completeUrl + checkout.released webhook

// Always keep full-page checkoutUrl as fallback when embed is blocked.
Troubleshooting

Common integration issues

Errors we see most often when partners first wire up protected checkout.

400 — returnUrl origin is not allowed

Register your storefront origin on the partner record (allowedRedirectOrigins). Paths and query strings are fine; only the origin is checked.

400 — Phone must be a valid Nigerian E.164 number

Send buyerPhone as +234XXXXXXXXXX (13 chars). Normalize local 080… numbers server-side before calling createCheckout.

Embed opens but buyer stays on OTP screen after verifying

Buyer must complete OTP on Trust Layer. If seller phone equals buyer phone, the session still works — ensure your onPaid handler navigates to returnUrl, not only closes the modal.

Webhook checkout.funded never arrives

Configure webhookUrl on your partner record. Events are best-effort with retries; also handle trustlayer:paid in the embed or poll your verify page.

Reusing the same cart checkoutId shows an old payment date

One checkoutId maps to one transaction row. When Trust Layer funds a reused checkout, update paidAt/updatedAt locally — do not rely on createdAt from an earlier card attempt.

403 on dispatch/deliver

Only escrows originated by your partner key (originatorPartnerId) can be updated via the partner API.

Embed script blocked or modal empty

Fall back to checkoutUrl full-page redirect. Pass baseUrl when testing against staging.

Try it live

Partner API playground

Run real requests against checkout, escrow, and reputation endpoints. Mint a checkout, copy cURL for your backend, open the embed modal, and watch client events — all in one place.

Open API playground

Requires a partner test key — email partners@trustlayer.africa

Need staging keys or origin allowlisting?

We help partners through first checkout, webhook verification, and storefront redirect setup.

partners@trustlayer.africa
Request partner access
Trust Layer

© 2026 Trust Layer · Partner integration docs