Emprise
API reference · v1

Accept a payment in one request.

Base path https://crypto.emprise.app/v1. JSON in, JSON out. Create a session, hand the hosted URL to your customer, and let Emprise verify the transaction and notify your server.

Paste the full API brief into Claude, Cursor or ChatGPT and ask it to build your integration.
POST/v1/pay

Quickstart

The entire integration in one call. Authorise with your API key, send the chain, token and amount, and receive a hosted URL to give your customer.

curl -X POST https://crypto.emprise.app/v1/pay \
  -H "Authorization: Bearer $EMPRISE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "chain": "base",
    "token": "USDC",
    "amount": "0.05",
    "idempotency_key": "ord_9f2a",
    "success_url": "https://yourapp.com/order/done",
    "cancel_url":  "https://yourapp.com/order/cancel"
  }'
Authentication

Authentication

Merchant endpoints use a bearer API key in the Authorization header. The server hashes your key with SHA-256 and compares it to the stored hash using a constant-time compare. Keep the key on your server; the public endpoints used by the checkout page never require or reveal it.

Header: Authorization: Bearer <api_key>. Invalid or missing returns 401. Public endpoints take only the session key and never return merchant secrets.
POST/v1/pay

Create a session or a permanent link

Send chain and token (and amount for fixed mode). Add a slug to claim a permanent, reusable /pay/{slug} link instead of a one-shot session. Omit amount for open-amount mode, where the customer chooses how much to pay.

Request body

{
  "chain": "ethereum",        // ethereum|base|arbitrum|polygon|bsc|tron|solana
  "token": "USDC",            // USDC|USDT, or native gas token (ETH/BNB/POL/TRX/SOL)
  "amount": "0.05",
  "recipient": "0xYourWallet",// optional; defaults to merchant payout address
  "expires_in": 1800,
  "idempotency_key": "ord-123",
  "success_url": "https://yourapp.com/done",
  "cancel_url": "https://yourapp.com/cancel",
  "webhook_url": "https://yourapp.com/hooks/emprise",
  "config": { "title": "Order #1234", "merchant_name": "Acme", "accent": "#4f46e5" }
}

Responses

{
  "id": "0192f8e1-7c3b-7b2a-9e1f-4d6b8a2c1e0d",
  "url": "https://crypto.emprise.app/pay/0192f8e1-7c3b-7b2a-9e1f-4d6b8a2c1e0d",
  "status": "pending",
  "expires_at": 1722865800
}

The server resolves chain + token to a family, chain id, token address and decimals, and converts the amount to atomic units with decimal.js. A slug must match ^[a-z0-9-]+$, is globally unique and immutable.

GET/v1/pay/{id}

Retrieve, list and update

Fetch one session or link by id (merchant-scoped: 404 if it belongs to another merchant), page through all of them, or update a permanent link's config, amount or status.

GET /v1/pay?type=session&status=pending&limit=50&cursor=...

{
  "items": [
    {
      "id": "0192f8e1-...",
      "status": "pending",
      "chain": "base",
      "token": "USDC",
      "amount": "0.05",
      "created_at": 1722864000,
      "expires_at": 1722865800
    }
  ],
  "next_cursor": "0192f8e2-..."
}
POST/v1/pay/{id}/settle

Submit a payment for verification

The hosted page calls this with the customer's transaction hash. Emprise verifies it on-chain against the right recipient, token and amount, marks the session paid, records the payer and (for open mode) the actual amount, then fires the webhook. It is idempotent: re-settling a paid session returns 200.

POST /v1/pay/0192f8e1-.../settle
{
  "tx_hash": "0x9a3f...e21",
  "from": "0xPayerAddress"
}
evm
Receipt + Transfer log to recipient, confirmations met
tron
TronGrid TRC-20 transfer to recipient
solana
SPL token transfer to recipient

Fixed mode requires on-chain value >= amount_atomic (overpayments accepted). Open mode records the actual value (must be >= min_amount in atomic units). Unconfirmed or not-found returns pending so the page keeps polling; wrong recipient, reverted, or under-min returns failed.

POSTWebhook · server to merchant

Signed webhooks

On a verified payment Emprise POSTs the event to your webhook_url with a Stripe-style HMAC signature. Verify the signature over the RAW body, reject stale timestamps, and dedupe by event_id.

Payload

POST https://yourapp.com/hooks/emprise
Content-Type: application/json
X-Emprise-Signature: t=1722864000,v1=7f3a9b...c21

Verify the signature (Node)

import crypto from "node:crypto";

export function verifyEmprise({ headers, rawBody, secret, toleranceSec = 300 }) {
  const sig = headers["x-emprise-signature"] ?? "";     // t=<unix>,v1=<hex>
  const map = Object.fromEntries(sig.split(",").map(p => p.split("=")));
  const t = Number(map.t);
  if (!t) throw new Error("missing timestamp");
  if (Math.abs(Date.now() / 1000 - t) > toleranceSec) throw new Error("stale timestamp");

  // HMAC over "<t>." + the RAW request body (not the parsed JSON).
  const expected = crypto
    .createHmac("sha256", secret)
    .update(`${t}.${rawBody}`)
    .digest("hex");

  const a = Buffer.from(expected);
  const b = Buffer.from(map.v1 ?? "");
  if (a.length !== b.length || !crypto.timingSafeEqual(a, b)) {
    throw new Error("invalid signature");
  }
  return true;   // safe to trust the body and fulfil the order
}

Always confirm server-side. The customer-facing redirect URL carries ?session=&status= for convenience only; a customer can edit those params. Treat the webhook (or GET /v1/pay/{id}) as the source of truth.

Errors

Errors

Every error uses one safe shape. The status code is also on the response. No stack traces, queries or internals are ever exposed.

{
  "error": {
    "code": "validation",
    "message": "Unsupported token XYZ on Base."
  }
}

Build it with your AI agent

Copy the full API brief and paste it into Claude, Cursor or ChatGPT. It includes every endpoint, the webhook signature scheme, and a ready integration task.