unbacked

API & MCP

Run offers from your servers and agents: REST API, sales tracking, webhooks and an MCP server.

REST API

Base URL https://staging.unbacked.xyz/api/v1. Send Authorization: Bearer ‹key› with a key from Integrations; each key has scopes. JSON in and out; amounts are strings in the offer token's base units (USDG has 6 decimals). The OpenAPI description is at https://staging.unbacked.xyz/api/v1/openapi.json.

curl https://staging.unbacked.xyz/api/v1/offers?mine=true \
  -H "Authorization: Bearer ubk_…"

GET  /offers                    live offers (?platform, ?model, ?q)
POST /offers                    create an offer            offers:write
GET  /offers/{offer}            one offer
GET  /offers/{offer}/budget     your offer's budget        offers:read
GET  /offers/{offer}/stats      posts, views, clicks, sales offers:read
GET  /offers/{offer}/submissions  posts (?pending=true)    submissions:read
POST /offers/{offer}/join       join as a creator          submissions:write
POST /offers/{offer}/submissions  submit a post            submissions:write
POST /submissions/{id}/review   approve or reject          submissions:write
POST /conversions               report a conversion        conversions:write
GET  /offers/{offer}/conversions  conversions              offers:read
POST /conversions/{id}/reject   reject in review           conversions:write
GET  /me/earnings               creator earnings           earnings:read
GET  /leaderboard               season top creators

Program widget

Put your offers on your site with one line of code. The widget takes your site's colours and logo by itself and lists your open offers with a Join button; creators join on Unbacked and become your referrals. Anyone may embed it, partner-program aggregators included. Your program also has its own page at https://staging.unbacked.xyz/p/your-project.

<!-- On your site, where the program should appear -->
<script src="https://staging.unbacked.xyz/embed.js" data-program="your-project" async></script>
<!-- optional: data-lang="es"  data-target="element-id" -->

GET  /program            your widget: code, page, colours, numbers
PUT  /program            { "siteUrl": "yourbrand.com" } → colours from your site
GET  /programs/{slug}    public: colours and open offers, as JSON
MCP  setup_widget · get_widget

Server-to-server conversions

POST https://staging.unbacked.xyz/api/v1/conversions with the conversion, signed: X-Unbacked-Signature: sha256=‹HMAC-SHA256 of the raw body with your signing secret›. The same external_id twice is one conversion. A conversion waits out your review window, when you can reject refunds and chargebacks; then it earns, and the money unlocks 72 hours after it is in a payout root. Pass tx_hash for on-chain conversions and we check the transaction on Robinhood Chain.

BODY='{"offer_id":"your-offer","click_id":"clk_4Vq9m2…","event":"purchase",
  "amount":49.00,"currency":"USD","external_id":"order_10293"}'
SIG=$(printf '%s' "$BODY" | openssl dgst -sha256 -hmac "$UNBACKED_SIGNING_SECRET" | cut -d' ' -f2)
curl https://staging.unbacked.xyz/api/v1/conversions -H "Content-Type: application/json" \
  -H "X-Unbacked-Signature: sha256=$SIG" -d "$BODY"

Stripe

Connect a restricted key in Integrations and we add a webhook endpoint to your Stripe. Put the ubk value into the Checkout Session's metadata.ubk or client_reference_id, or let creators' promotion codes do it: we create one per creator from your coupon.

const session = await stripe.checkout.sessions.create({
  // …your line items and URLs
  metadata: { ubk: clickIdFromCookie },   // or client_reference_id: clickIdFromCookie
  allow_promotion_codes: true,             // creators' codes are created from your coupon
});

Event hooks

Every CPA and revenue-share offer has a secret live address and a test address (the offer page, Event hooks). Point your shop's or billing's webhook at them: the offer's deployed rules, set up with the affiliate agent, take the order id, amount, promo code and click id out of your payload, and each conversion then goes through the usual checks and review window. Test events are never paid; the agent simulates its rules on them. We can also check your source's own HMAC-SHA256 signature. Up to 600 live and 60 test events a minute, 64 KB each.

curl https://staging.unbacked.xyz/api/hooks/hkt_…/test -H "Content-Type: application/json" -d '{
  "id": 5512331, "topic": "orders/paid", "total_price": "59.00", "currency": "USD",
  "discount_codes": [{ "code": "ALEX20" }],
  "note_attributes": [{ "name": "ubk", "value": "clk_4Vq9m2…" }]
}'

Webhooks

Events: submission.created, submission.awaiting_approval, conversion.accepted, budget.low. Every POST carries X-Unbacked-Timestamp and X-Unbacked-Signature: sha256=HMAC(secret, "‹timestamp›.‹body›"). Reject events older than 5 minutes and dedupe on the event id.

import { verifyWebhook } from "@unbacked/sdk";

const ok = await verifyWebhook(secret, rawBody, {
  timestamp: request.headers.get("x-unbacked-timestamp"),
  signature: request.headers.get("x-unbacked-signature"),
});

MCP server

Add https://staging.unbacked.xyz/api/mcp to Claude or any MCP client. It signs in with OAuth (you approve the app and what it may do) or takes an API key as a bearer token. Tools act only on your own account: offers, posts, conversions and budgets for projects; offer search, joining, posting and earnings for creators.

{
  "mcpServers": {
    "unbacked": { "url": "https://staging.unbacked.xyz/api/mcp" }
  }
}

TypeScript SDK

The @unbacked/sdk package wraps the API, signs server-to-server bodies and verifies our webhooks. It runs anywhere fetch and Web Crypto exist.

import { Unbacked } from "@unbacked/sdk";

const unbacked = new Unbacked({ apiKey: process.env.UNBACKED_API_KEY! });
await unbacked.conversions.report({
  offer_id: "your-offer", promo_code: "ALEX20", event: "purchase",
  amount: 49, currency: "USD", external_id: "order_10293",
});