Developer quickstart

This is the fastest reliable path from a new Hilt workspace or Hilt Pay API setup intent to one working developer integration. By the end of this page, you should have:
  • one product created from code, or one Hilt Pay API setup manifest ready for owner approval
  • one webhook endpoint with a signed test delivery
  • one sandbox session with fake payment, membership, receipt, or entitlement objects
  • one clear path to production settlement once the owner is ready
This page is intentionally practical. It optimizes for one working flow, not full API coverage.

30-second win

If you only want to prove the Hilt developer surface is alive before doing anything deeper, run these two reads first.

1. Prove your API key works

curl https://api.hilt.so/v1/products \
  -H "X-Hilt-Key: hk_live_..."
Success looks like:
  • a 200 response
  • a JSON array or product list payload from your workspace
Representative response:
[
  {
    "id": "ae9673c8-95db-4b39-bc2c-b5e6d5dfd9d3",
    "slug": "example-slug",
    "title": "Hilt Quickstart Access",
    "status": "ACTIVE"
  }
]

2. Prove sandbox is available

curl https://api.hilt.so/v1/testing/scenarios \
  -H "X-Hilt-Key: hk_sandbox_..."
Success looks like:
  • a 200 response
  • a scenarios list that includes confirmed_access, payment_failed, delivery_failed, and renewal_grace
Representative response:
{
  "count": 4,
  "scenarios": [
    { "id": "confirmed_access", "title": "Confirmed access" },
    { "id": "payment_failed", "title": "Payment failed" },
    { "id": "delivery_failed", "title": "Delivery failed" },
    { "id": "renewal_grace", "title": "Renewal grace" }
  ]
}
If both commands work, your auth, base URL, and testing surface are in place. From there, the rest of this page becomes a straight line.

API-first shortcut for agents

If an agent is building a paid API, bot, dataset, or private product, start with Agent setup instead of manually creating a dashboard key. The agent path is:
  1. create a sandbox setup intent with POST /v1/access/agent-bootstrap
  2. submit a setup manifest for app, product, webhook, protected resource, protocol, and rail
  3. let the owner approve billing, live keys, payout settings, and live mode
  4. create payment sessions and check entitlements through Hilt Pay API
The merchant dashboard remains the control plane for owner approval, billing, payout wallets, rail settings, emergency disable, and audit review.

Before you start

Have these ready:
  • a Hilt workspace with Hilt Pay enabled
  • one payout wallet you actually want to use
  • one post-payment destination such as a redirect, download, Telegram flow, or Discord flow
  • one API key from Dashboard -> Advanced
  • for Hilt Pay API, one owner-approved setup intent or scoped API key with access:read, access:write, and optionally access:webhooks
  • one sandbox API key if you want to call the testing routes over API
  • one dashboard session token if you want to manage webhook endpoints by API instead of the dashboard UI

1. Set your environment variables once

Use the live API base URL unless you are explicitly testing a non-production environment.
export HILT_API_URL="https://api.hilt.so"
export HILT_API_KEY="hk_live_..."
export HILT_SANDBOX_KEY="hk_sandbox_..."
export HILT_BEARER_TOKEN="JWT_TOKEN"
export HILT_MERCHANT_WALLET="So1anaMerchantWallet1111111111111111111111111"
What each value is for:
  • HILT_API_KEY: merchant backend routes
  • HILT_SANDBOX_KEY: sandbox testing routes
  • HILT_BEARER_TOKEN: webhook endpoint management only
  • HILT_MERCHANT_WALLET: the payout wallet used in the example product
Checkpoint:
  • you can now run merchant API calls with X-Hilt-Key
  • you can now create webhook endpoints with Authorization: Bearer ...

Node .env.local

HILT_API_URL=https://api.hilt.so
HILT_API_KEY=hk_live_...
HILT_SANDBOX_KEY=hk_sandbox_...
HILT_BEARER_TOKEN=JWT_TOKEN
HILT_MERCHANT_WALLET=So1anaMerchantWallet1111111111111111111111111
HILT_WEBHOOK_SECRET=whsec_...

Python .env

HILT_API_URL=https://api.hilt.so
HILT_API_KEY=hk_live_...
HILT_SANDBOX_KEY=hk_sandbox_...
HILT_BEARER_TOKEN=JWT_TOKEN
HILT_MERCHANT_WALLET=So1anaMerchantWallet1111111111111111111111111
HILT_WEBHOOK_SECRET=whsec_...

2. Install one client surface

Use whichever surface matches how you work:

TypeScript

npm install @hiltpay/sdk

Python

pip install hilt-sdk

Postman

Use the public developer-assets repo:
  • https://github.com/Hiltpay/hilt-developer-assets
Import:
  • postman/hilt-postman-collection.json
  • postman/hilt-postman-environment.json
If you prefer raw HTTP first, stay with the cURL examples below and move to the SDK or Postman pages afterwards.

3. Create one one-off product

Start with a simple product that gives you the full Hilt trail:
  • price
  • checkout payload
  • payment
  • membership
  • receipt
  • support and audit context later
curl -X POST "$HILT_API_URL/v1/products" \
  -H "X-Hilt-Key: $HILT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "product_type": "PAYMENT_LINK",
    "title": "Hilt Quickstart Access",
    "description": "One-off access for integration testing",
    "amount_minor_units": 29000000,
    "token_mint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
    "merchant_wallet": "'"$HILT_MERCHANT_WALLET"'",
    "delivery_type": "REDIRECT",
    "delivery_value": "https://example.com/hilt/thanks",
    "membership_config": {
      "enabled": true,
      "platform": "CUSTOM",
      "identity_type": "WALLET",
      "identity_required": false,
      "renewal_mode": "ONE_OFF"
    }
  }'
Representative response:
{
  "id": "ae9673c8-95db-4b39-bc2c-b5e6d5dfd9d3",
  "slug": "example-slug",
  "status": "ACTIVE",
  "product_type": "PAYMENT_LINK",
  "title": "Hilt Quickstart Access",
  "amount_minor_units": 29000000
}
Save these immediately:
  • product.id
  • product.slug
Checkpoint:
  • the product appears in the dashboard
  • GET /v1/products/{product_id} returns the same commercial object

4. Create one webhook endpoint and send a signed test event

Webhook endpoint management uses a workspace session token, not an API key. If you do not want to create the endpoint over API, create it once in Dashboard -> Advanced and then continue with the test-event and sandbox steps.

Create the endpoint

curl -X POST "$HILT_API_URL/v1/webhooks/endpoints" \
  -H "Authorization: Bearer $HILT_BEARER_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "label": "Quickstart receiver",
    "url": "https://example.com/hilt/webhooks",
    "subscribed_events": [
      "payment.confirmed",
      "receipt.created",
      "membership.activated",
      "delivery.failed"
    ],
    "product_ids": []
  }'
Representative response:
{
  "endpoint": {
    "id": "wh_123",
    "label": "Quickstart receiver",
    "status": "active"
  },
  "signing_secret": "whsec_..."
}
Store the signing secret immediately. Hilt only returns the full secret on creation or rotation.

Send a signed test event

curl -X POST "$HILT_API_URL/v1/webhooks/endpoints/wh_123/test" \
  -H "Authorization: Bearer $HILT_BEARER_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "event_type": "payment.confirmed"
  }'
Checkpoint:
  • the endpoint exists in Hilt
  • your receiver gets a signed request
  • the delivery log shows success or a concrete failure reason

Smallest possible webhook receiver: Node

If you want the shortest possible Node receiver that can go green on a signed test event, use this:
import crypto from "node:crypto";
import express from "express";

const app = express();
const signingSecret = process.env.HILT_WEBHOOK_SECRET!;

function verifySignature(rawBody: Buffer, signatureHeader: string) {
  const parts = Object.fromEntries(
    signatureHeader.split(",").map((part) => {
      const [key, value] = part.split("=", 2);
      return [key, value];
    }),
  );

  const timestamp = parts.t;
  const received = parts.v1;
  if (!timestamp || !received) {
    throw new Error("Malformed Hilt signature header");
  }

  const expected = crypto
    .createHmac("sha256", signingSecret)
    .update(`${timestamp}.${rawBody.toString("utf8")}`)
    .digest("hex");

  const valid = crypto.timingSafeEqual(
    Buffer.from(expected, "utf8"),
    Buffer.from(received, "utf8"),
  );

  if (!valid) {
    throw new Error("Invalid Hilt webhook signature");
  }
}

app.post("/hilt/webhooks", express.raw({ type: "application/json" }), (req, res) => {
  const rawBody = req.body as Buffer;
  const signatureHeader = req.header("X-Hilt-Signature") ?? "";

  verifySignature(rawBody, signatureHeader);

  const event = JSON.parse(rawBody.toString("utf8"));
  console.log("hilt event", event.id, event.type, event.livemode);

  res.status(200).json({ ok: true });
});

app.listen(3000, () => {
  console.log("Listening on http://localhost:3000/hilt/webhooks");
});

Smallest possible webhook receiver: Python

import hashlib
import hmac
import json
import os

from fastapi import FastAPI, HTTPException, Request

app = FastAPI()
signing_secret = os.environ["HILT_WEBHOOK_SECRET"]


def verify_signature(raw_body: bytes, signature_header: str) -> None:
    parts = {}
    for chunk in signature_header.split(","):
        key, value = chunk.split("=", 1)
        parts[key] = value

    timestamp = parts.get("t")
    received = parts.get("v1")
    if not timestamp or not received:
        raise ValueError("Malformed Hilt signature header")

    expected = hmac.new(
        signing_secret.encode("utf-8"),
        f"{timestamp}.{raw_body.decode('utf-8')}".encode("utf-8"),
        hashlib.sha256,
    ).hexdigest()

    if not hmac.compare_digest(expected, received):
        raise ValueError("Invalid Hilt webhook signature")


@app.post("/hilt/webhooks")
async def hilt_webhooks(request: Request):
    raw_body = await request.body()
    signature_header = request.headers.get("X-Hilt-Signature", "")

    try:
        verify_signature(raw_body, signature_header)
    except ValueError as exc:
        raise HTTPException(status_code=400, detail=str(exc)) from exc

    event = json.loads(raw_body.decode("utf-8"))
    print("hilt event", event["id"], event["type"], event["livemode"])

    return {"ok": True}
Success looks like:
  • Hilt sends a signed test event
  • your receiver logs the event id and type
  • Hilt marks the delivery as successful

5. Run one sandbox session

Now validate your API handling without using a real payment.
curl -X POST "$HILT_API_URL/v1/testing/sessions" \
  -H "X-Hilt-Key: $HILT_SANDBOX_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "scenario": "confirmed_access",
    "product_id": "ae9673c8-95db-4b39-bc2c-b5e6d5dfd9d3",
    "identity_display": "@sandboxbuyer"
  }'
Representative response:
{
  "session": {
    "id": "6f9418d1-cf8e-4fcb-9b0d-90b31f407111",
    "scenario": "confirmed_access",
    "status": "complete",
    "fake_payment_id": "sandbox_pay_0b0fbaf8d8b844b38b",
    "fake_membership_id": "sandbox_mem_76fbb6674f1e4e7d9d",
    "fake_receipt_id": "sandbox_rcpt_4c56f442f8274e7d8f",
    "event_count": 3,
    "delivery_count": 1
  },
  "scenario": {
    "id": "confirmed_access",
    "title": "Confirmed access"
  }
}
What you should verify:
  • your webhook consumer receives livemode=false events
  • your application can handle payment.confirmed, receipt.created, and membership.activated
  • you can inspect the fake ids later through the returned sandbox session

6. Check live readiness

Sandbox proves your Hilt API handling. Before real buyer traffic, make sure the owner has confirmed the live payout wallet, product settings, webhook destination, and API key scopes. If the merchant wants to verify the real wallet path before opening the product, complete a low-value live settlement check and compare the payment, receipt, membership, and webhook trail afterwards. If you want to start the session from your own app flow, this is the connect call:
curl -X POST "$HILT_API_URL/v1/products/p/example-slug/connect" \
  -H "Content-Type: application/json" \
  -d '{
    "payer_wallet": "BuyerWallet1111111111111111111111111111111111"
  }'
Checkpoint:
  • the product is ready for live buyer sessions
  • webhook and sandbox handling are proven
  • any optional live settlement check has a payment_id, receipt, and event trail

7. Verify the Hilt trail

Use Hilt as the source of truth after payment.

Payment state

curl "$HILT_API_URL/v1/payments/PAYMENT_ID"

Membership state

curl "$HILT_API_URL/v1/memberships?product_id=ae9673c8-95db-4b39-bc2c-b5e6d5dfd9d3&limit=20" \
  -H "X-Hilt-Key: $HILT_API_KEY"

Receipt proof

curl "$HILT_API_URL/v1/receipts?page=1&per_page=20&product_id=ae9673c8-95db-4b39-bc2c-b5e6d5dfd9d3" \
  -H "X-Hilt-Key: $HILT_API_KEY"
Expected result:
  • payment is visible and reaches CONFIRMED
  • membership exists if the flow should create access
  • receipt exists and has a public proof URL
  • webhook delivery log shows the real outbound event trail

Common first-launch mistakes

  • using an API key for webhook endpoint management
  • skipping the signed test event and only discovering signature problems on the first real payment
  • treating sandbox as proof of wallet or payout correctness
  • launching multiple products before one path is proven

Common questions

What is the fastest safe Hilt developer quickstart?

Create one product, add one webhook endpoint, run one signed test event, create one sandbox session, then confirm live readiness before real buyer traffic.

Does the quickstart require a live payment?

No. Sandbox is the developer quickstart. A low-value live settlement check is useful when the merchant wants to verify the real wallet flow, Solana confirmation path, payout routing, receipt creation, and dashboard trail before opening a product to buyers.

Should I start with SDKs or raw HTTP?

Use the SDK or Postman if you want a faster guided path. Use raw HTTP when you need to see every route, header, and payload directly.

Where to go next