Examples

Use this page when you want end-to-end starting points rather than route-by-route explanation.

Base URLs

Marketing site:  https://www.hilt.so
Merchant app:    https://app.hilt.so
API:             https://api.hilt.so
Docs:            https://docs.hilt.so

Auth patterns

Server-side merchant automation normally uses:
X-Hilt-Key: hk_live_...
Session-assisted merchant tools can use:
Authorization: Bearer JWT_TOKEN

Example 1: Create a hosted access product

cURL

curl -X POST https://api.hilt.so/v1/products \
  -H "X-Hilt-Key: hk_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "product_type": "PAYMENT_LINK",
    "title": "Members lounge",
    "description": "Monthly access",
    "amount_minor_units": 200000,
    "token_mint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
    "merchant_wallet": "So1anaMerchantWallet1111111111111111111111111",
    "delivery_type": "REDIRECT",
    "delivery_value": "https://example.com/welcome"
  }'

TypeScript

const response = await fetch("https://api.hilt.so/v1/products", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "X-Hilt-Key": process.env.HILT_API_KEY!,
  },
  body: JSON.stringify({
    product_type: "PAYMENT_LINK",
    title: "Discord membership",
    description: "Monthly paid access",
    amount_minor_units: 29000000,
    token_mint: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
    merchant_wallet: "So1anaMerchantWallet1111111111111111111111111",
    delivery_type: "DISCORD_INVITE",
    delivery_value: "https://discord.gg/example",
  }),
});

if (!response.ok) {
  const error = await response.json().catch(() => ({}));
  throw new Error(error.detail ?? `Hilt request failed: ${response.status}`);
}

const product = await response.json();
console.log(product.id, product.slug);
Use this when your own bot or backend already knows who the buyer is before checkout.
curl -X POST https://api.hilt.so/v1/products/PRODUCT_ID/handoff-link \
  -H "X-Hilt-Key: hk_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "identity_type": "TELEGRAM_USER_ID",
    "identity_value": "728255790",
    "identity_display": "@buyer",
    "expires_in_minutes": 30
  }'
Representative response:
{
  "handoff_token": "hilt_handoff_...",
  "expires_at": "2026-04-16T18:30:00Z",
  "checkout_url": "https://www.hilt.so/p/example-slug?handoff=hilt_handoff_..."
}

Example 3: Start a buyer session and keep the payment id

cURL

curl -X POST https://api.hilt.so/v1/products/p/example-slug/connect \
  -H "Content-Type: application/json" \
  -d '{
    "payer_wallet": "BuyerWallet1111111111111111111111111111111111"
  }'

What to keep from the response

Persist:
  • payment_id
  • amount_minor_units
  • merchant_amount_minor_units
  • asset_symbol
  • expires_at

Example 4: Poll payment status

cURL

curl https://api.hilt.so/v1/payments/PAYMENT_ID

TypeScript

type PaymentStatus = {
  id: string;
  product_id: string;
  status: string;
  tx_signature?: string | null;
  delivery_status?: string | null;
};

async function getPayment(paymentId: string): Promise<PaymentStatus> {
  const response = await fetch(`https://api.hilt.so/v1/payments/${paymentId}`);
  if (!response.ok) {
    throw new Error(`Unable to read payment ${paymentId}: ${response.status}`);
  }
  return response.json();
}

const payment = await getPayment("f0f4e620-1ca3-4fc8-b0ba-2d04342fe467");

switch (payment.status) {
  case "CONFIRMED":
    console.log("ready for member lookup");
    break;
  case "PENDING_CONFIRMATION":
    console.log("keep waiting with the same payment_id");
    break;
  default:
    console.log("current status:", payment.status);
}

Python

import requests

def get_payment(payment_id: str):
    response = requests.get(
        f"https://api.hilt.so/v1/payments/{payment_id}",
        timeout=20,
    )
    response.raise_for_status()
    return response.json()

payment = get_payment("f0f4e620-1ca3-4fc8-b0ba-2d04342fe467")
print(payment["status"], payment.get("tx_signature"))

Rust

use reqwest::Client;
use serde::Deserialize;

#[derive(Debug, Deserialize)]
struct Payment {
    id: String,
    status: String,
    tx_signature: Option<String>,
}

async fn get_payment(payment_id: &str) -> Result<Payment, reqwest::Error> {
    let client = Client::new();
    client
        .get(format!("https://api.hilt.so/v1/payments/{payment_id}"))
        .send()
        .await?
        .error_for_status()?
        .json::<Payment>()
        .await
}

Example 5: Read the post-payment trail

Look up a membership

curl "https://api.hilt.so/v1/memberships/lookup?identity=@buyer" \
  -H "X-Hilt-Key: hk_live_..."

Read receipts in Python

import requests

response = requests.get(
    "https://api.hilt.so/v1/receipts",
    headers={"X-Hilt-Key": "hk_live_..."},
    params={"page": 1, "per_page": 20},
    timeout=20,
)
response.raise_for_status()
print(response.json())

Example 6: Open a support ticket

curl -X POST https://api.hilt.so/v1/support/tickets \
  -H "X-Hilt-Key: hk_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "subject": "Buyer needs help",
    "category": "OTHER",
    "body": "Payment confirmed, but the buyer still needs a manual handoff."
  }'

Example 7: Continue a support thread

curl -X POST https://api.hilt.so/v1/support/tickets/hilt_ticket_R64eNsqH_nntOg/message \
  -H "X-Hilt-Key: hk_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "body": "We retried delivery and confirmed the membership is active."
  }'

Example 8: Worker loop after checkout

If your application already has queue workers or webhook consumers, a small polling worker usually gives the cleanest Hilt integration.

TypeScript

async function waitForConfirmedPayment(paymentId: string) {
  for (let attempt = 0; attempt < 20; attempt += 1) {
    const payment = await getPayment(paymentId);

    if (payment.status === "CONFIRMED") {
      return payment;
    }

    if (payment.status === "FAILED") {
      throw new Error(`Payment ${paymentId} failed`);
    }

    await new Promise((resolve) => setTimeout(resolve, 5000));
  }

  throw new Error(`Payment ${paymentId} did not settle in time`);
}

Python

import time

def wait_for_confirmed_payment(payment_id: str):
    for _ in range(20):
        payment = get_payment(payment_id)
        if payment["status"] == "CONFIRMED":
            return payment
        if payment["status"] == "FAILED":
            raise RuntimeError(f"Payment {payment_id} failed")
        time.sleep(5)

    raise RuntimeError(f"Payment {payment_id} did not settle in time")

Example rule of thumb

The simplest reliable pattern is:
  1. let Hilt run checkout and settlement
  2. keep the payment_id
  3. let your backend react to Hilt state afterwards
That is usually cleaner than rebuilding the payment, membership, receipt, and support state machine yourself.