Docs/Items & Purchases

Items & Purchases

Items are one-time purchasable things — templates, credits, digital goods, access passes. Register them once, let buyers pay through hosted Midtrans checkout, get a webhook when money lands.

Items vs subscriptions vs shop payments

TypeUse when
ItemsYou have a catalog of things to sell. Register items, buyers pick one, pay the listed price (or a dynamic override).
SubscriptionsYou have pricing tiers. Payment auto-assigns a plan claim to the user's JWT.
Shop paymentsYou manage your own catalog. Just pass an amount and order ID — no item registration needed.

How it works

1
Register items

Create items via the builder API or dashboard. Set a name, price, and optional webhook URL.

2
Buyer purchases

Call the public purchase endpoint with the buyer's email and item ID. Get back a Midtrans checkout URL.

3
Webhook on settlement

When payment settles, we POST an item.purchased event to your webhook URL.

Create an item

Items are created via the builder API (requires your session cookie) or directly in the dashboard under Items → Catalog.

create-item.tstypescript
const res = await fetch("https://astapa.com/api/platform/items", {
  method: "PUT",
  headers: { "Content-Type": "application/json" },
  credentials: "include", // builder session cookie
  body: JSON.stringify({
    project_id: "your_project_id",
    name: "Premium Template",
    description: "A professionally designed template",
    price: 50000,       // IDR, stored as the default price
    currency: "IDR",
    webhook_url: "https://yourapp.com/webhooks/purchase", // optional
  }),
});

const { item } = await res.json();
// item.id is what buyers will reference

Public endpoints (no auth)

These are called from your frontend or your buyer's browser. No credentials needed — just the client_id.

Dynamic pricing — override the price at checkout

Pass amount in the purchase request to override the item's stored price. Useful for donations, custom quotes, or variable-price products.

dynamic-purchase.tstypescript
// Buyer is paying a custom amount for a "donation" item
const res = await fetch("https://astapa.com/api/platform/public-items/purchase", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({
    client_id: process.env.NEXT_PUBLIC_ASTAPA_CLIENT_ID,
    item_id: "donation-item-id",
    buyer_email: "donor@example.com",
    amount: 150000, // buyer chose this amount — overrides item.price
    metadata: { campaign: "ramadan-2026" },
  }),
});

const { invoice_url } = await res.json();
window.location.href = invoice_url;
The stored price is just a default
If you pass amount, that's what gets charged and what shows up in the purchase record. The item's price field is only used as a fallback when amount is omitted.

Webhook on settlement

When a purchase settles, we POST to the webhook URL. Priority order: item-level webhook_url → project-level webhook_url. If neither is set, no webhook fires.

webhook-payload.jsonjson
{
  "event": "item.purchased",
  "order_id": "item-abc12345-1a2b3c4d",
  "item_id": "item-uuid",
  "item_name": "Premium Template",
  "buyer_email": "buyer@example.com",
  "buyer_name": "John Doe",
  "amount": 50000,
  "currency": "IDR",
  "paid_at": "2026-05-06T10: 00: 00Z",
  "metadata": { "ref": "campaign-42" }
}
Set a project-level webhook URL to catch everything
Instead of setting a webhook URL per item, set one on the project (Settings tab). It covers all item purchases, shop payments, and subscription payments in one place.

View purchases

Retry failed webhooks from the dashboard
Payments → Items tab shows webhook_status per purchase. If it's failed, hit Retry. Or call POST /api/platform/projects/[id]/item-purchases/[purchaseId]/retry-webhook.

Related

API Playground
Click "Try it" on any endpoint to get started.
Items & Purchases | Astapa