Skip to content
ABUNA
DocsQuickstart

Getting started

Quickstart

Create a product and price, send a customer to a checkout session, and receive the webhook, in Test mode.

In this guide you create a product and a price in Test mode, send a customer to a checkout session, pay with a simulated payment, and receive the webhook. No money moves.

Get your Test key

Sign up and create an app in the dashboard. Abuna then shows a secret key and a webhook signing secret for Test and for Live. It shows them only once, so save them.

Open the app in Test mode. Its app ID is the part of the dashboard URL after /console/. Use that ID in every path and the sk_test_ key in the Authorization header. The examples use the app ID 01J9ZQ4Y7R3T6V8W2X5B1C0DEF.

Lost a key? In Settings, open API keys and create a new one.

Create a product

A product is what you sell.

POST /v1/apps/{appID}/products
curl https://api.abuna.app/v1/apps/01J9ZQ4Y7R3T6V8W2X5B1C0DEF/products \
  -H "Authorization: Bearer sk_test_..." \
  -H "Content-Type: application/json" \
  -d '{"name": "Pro plan"}'
Response
{
  "id": "01J9ZQ5A2B3C4D5E6F7G8H9JKM",
  "app_id": "01J9ZQ4Y7R3T6V8W2X5B1C0DEF",
  "name": "Pro plan",
  "slug": "pro-plan",
  "created_at": 1727600000
}

Create a price

Pass the product ID from the last response. This price charges FCFA 5,000 every month. Amounts are integers in the currency's smallest unit.

POST /v1/apps/{appID}/prices
curl https://api.abuna.app/v1/apps/01J9ZQ4Y7R3T6V8W2X5B1C0DEF/prices \
  -H "Authorization: Bearer sk_test_..." \
  -H "Content-Type: application/json" \
  -d '{"product_id": "01J9ZQ5A2B3C4D5E6F7G8H9JKM", "name": "Monthly", "currency": "XAF", "amount": 5000, "interval": "month"}'
Response
{
  "id": "01J9ZQ5N1P2Q3R4S5T6V7W8XYZ",
  "product_id": "01J9ZQ5A2B3C4D5E6F7G8H9JKM",
  "name": "Monthly",
  "currency": "XAF",
  "amount": 5000,
  "interval": "month",
  "interval_count": 1,
  "created_at": 1727600000,
  "archived_at": null
}

Set your webhook endpoint

In the dashboard, open Webhooks in Test mode and set your endpoint URL. It must be a public https URL. Only a team owner can set it, and only from the dashboard, not with a secret key.

Abuna records events even with no endpoint set, so you can skip this step and read them from the API later.

Create a checkout session

A checkout session is a hosted page for one customer. Create it with Start a subscription, from your server with your secret key, never from a browser. Put your own user ID in metadata, so the webhook tells you who paid.

POST /v1/apps/{appID}/subscriptions/start
curl https://api.abuna.app/v1/apps/01J9ZQ4Y7R3T6V8W2X5B1C0DEF/subscriptions/start \
  -H "Authorization: Bearer sk_test_..." \
  -H "Content-Type: application/json" \
  -d '{"price_id": "01J9ZQ5N1P2Q3R4S5T6V7W8XYZ", "success_url": "http://localhost:3000/welcome", "metadata": {"user_id": "42"}}'
Response
{
  "id": "01J9ZQ9S1T2V3W4X5Y6Z7A8B9C",
  "type": "checkout",
  "status": "open",
  "url": "https://app.abuna.app/checkout/7f2c9e4a1b8d3f6e0c5a9d2b7e4f1a8c3d6b",
  "success_url": "http://localhost:3000/welcome",
  "cancel_url": null,
  "metadata": {
    "user_id": "42"
  },
  "customer_id": null,
  "price_id": "01J9ZQ5N1P2Q3R4S5T6V7W8XYZ",
  "subscription_id": null,
  "expires_at": 1727686400,
  "completed_at": null,
  "created_at": 1727600000
}

The session lasts 24 hours. In Test mode, the success URL can be an http URL on localhost. Live mode needs https.

Send the customer to checkout

Redirect the customer to the session's url. They enter their name, email, and phone number. Abuna creates the customer and a pending subscription, then shows the pay step on the same page. In Test mode, it asks which simulated outcome to use. Pick Successful payment.

Once paid, the subscription is active and the session is completed. The done screen links back to your success URL, with session_id and subscription_id added to it. The Sessions guide covers prefilling the customer, the return, expiry, and retries.

To sell without writing code, share a checkout link. Open the product in the dashboard and copy the link next to the price. It looks like this, with your publishable key at the end:

Checkout link
https://app.abuna.app/web/checkout/01J9ZQ5N1P2Q3R4S5T6V7W8XYZ?publishkey=pk_test_...

Anyone with the link can subscribe, so you can put it on a page or in a message. It carries no metadata, so you match the customer to your own user by email. Once paid, the pay page links back to your app's success URL. Set it in Settings, under General.

Or subscribe a customer from your server

To skip checkout, create the customer yourself. A customer needs an email address and a phone number with its country code.

POST /v1/apps/{appID}/customers
curl https://api.abuna.app/v1/apps/01J9ZQ4Y7R3T6V8W2X5B1C0DEF/customers \
  -H "Authorization: Bearer sk_test_..." \
  -H "Content-Type: application/json" \
  -d '{"name": "Ana N.", "email": "ana@example.com", "phone_number": "+237671234567"}'
Response
{
  "app_id": "01M4047ET949K8JPEZ25NMS0T2",
  "created_at": 1791005801,
  "email": "ana@example.com",
  "id": "01M4047EVBPG18CRGW2PQRTJBE",
  "name": "Ana N.",
  "phone_number": "+237671234567"
}

Then start the subscription with the customer and price IDs. The response holds the pending subscription and its first period, the entry. Keep the entry's id for the next step.

POST /v1/apps/{appID}/subscriptions
curl https://api.abuna.app/v1/apps/01J9ZQ4Y7R3T6V8W2X5B1C0DEF/subscriptions \
  -H "Authorization: Bearer sk_test_..." \
  -H "Content-Type: application/json" \
  -d '{"customer_id": "01J9ZQ6A1B2C3D4E5F6G7H8J9K", "price_id": "01J9ZQ5N1P2Q3R4S5T6V7W8XYZ"}'
Response
{
  "entry": {
    "created_at": 1791005801,
    "ends_at": 1793684201,
    "grace_period_ends_at": 1791265001,
    "id": "01M4047EVDQHH81KPH3FFWPS3N",
    "in_force": true,
    "paid_at": null,
    "paused_at": null,
    "pay_token": "e1386d8a32683485e08a1860efb3e07f0515",
    "period_index": 0,
    "price_id": "01M4047ETGXAGTBEE39S725NC8",
    "starts_at": 1791005801,
    "subscription_id": "01M4047EVCVZWYX89GXY8NVCDP"
  },
  "subscription": {
    "activated_at": null,
    "app_id": "01M4047ET949K8JPEZ25NMS0T2",
    "cancel_at": null,
    "canceled_at": null,
    "created_at": 1791005801,
    "customer_id": "01M4047EVBPG18CRGW2PQRTJBE",
    "id": "01M4047EVCVZWYX89GXY8NVCDP",
    "manage_token": "9a4f4e28e87d95195921dc7a4917c3f0094e",
    "metadata": {},
    "price_id": "01M4047ETGXAGTBEE39S725NC8",
    "starts_at": 1791005801,
    "status": "pending"
  }
}

Abuna emails the customer a pay link for this invoice. In Test mode it records the email as an event instead of sending it. The customer has until grace_period_ends_at to pay, or Abuna cancels the subscription.

Pay the first invoice

Charge the entry from your server. In Test mode the simulated payment succeeds and moves no money. In Live mode this charges the customer's phone number through your provider.

POST /v1/apps/{appID}/entries/{id}/pay
curl -X POST https://api.abuna.app/v1/apps/01J9ZQ4Y7R3T6V8W2X5B1C0DEF/entries/01J9ZQ7E1F2G3H4J5K6M7N8P9Q/pay \
  -H "Authorization: Bearer sk_test_..."
Response
{
  "entry": {
    "created_at": 1791005801,
    "ends_at": 1793684201,
    "grace_period_ends_at": 1791265001,
    "id": "01M4047ETRRSRY6EY5FH0X9XCG",
    "in_force": true,
    "paid_at": 1791005801,
    "paused_at": null,
    "pay_token": "b2e4f7123dcd4e4a5a9dd86f308283cf5ec3",
    "period_index": 0,
    "price_id": "01M4047ETGXAGTBEE39S725NC8",
    "starts_at": 1791005801,
    "subscription_id": "01M4047ETPMMZ6EWNJQBD1S7W6"
  },
  "paid": true,
  "status": "succeeded"
}

The subscription is now active.

Receive the webhook

Whichever way the customer subscribed, Abuna records these events:

  • subscription.created and invoice.created when the subscription starts.
  • invoice.paid and subscription.paid when the first payment succeeds.
  • session.completed at the same moment, if the customer paid through a checkout session.

For a checkout session, grant access when session.completed arrives. It carries your metadata and the new subscription_id and customer_id. For a checkout link or a subscription you started from your server, grant access on subscription.paid, which Abuna records once per subscription, when it turns active.

Each request carries an X-Abuna-Signature header. Check it before you trust the body, as Receiving webhooks shows.

session.completed webhook body
{
  "created_at": 1791005801,
  "data": {
    "type": "checkout",
    "metadata": {
      "user_id": "42"
    },
    "price_id": "01M4047ETGXAGTBEE39S725NC8",
    "session_id": "01M4047ETJG42NP06J3EAHNFK7",
    "customer_id": "01M4047ETN25J0H6E8P8ZX9QGZ",
    "completed_at": 1791005801,
    "subscription_id": "01M4047ETPMMZ6EWNJQBD1S7W6"
  },
  "environment": "test",
  "id": "01M4047EV2EJVM461NPZ582427",
  "type": "session.completed"
}

To see what happened without an endpoint, list the app's events. The newest come first.

GET /v1/apps/{appID}/events
curl "https://api.abuna.app/v1/apps/01J9ZQ4Y7R3T6V8W2X5B1C0DEF/events?type=session.completed" \
  -H "Authorization: Bearer sk_test_..."
Response
[
  {
    "id": "01J9ZQ8E1F2G3H4J5K6M7N8P9Q",
    "event_type": "session.completed",
    "payload": {
      "session_id": "01J9ZQ9S1T2V3W4X5Y6Z7A8B9C",
      "type": "checkout",
      "subscription_id": "01J9ZQ6M1N2P3Q4R5S6T7V8W9X",
      "customer_id": "01J9ZQ6A1B2C3D4E5F6G7H8J9K",
      "price_id": "01J9ZQ5N1P2Q3R4S5T6V7W8XYZ",
      "metadata": {
        "user_id": "42"
      },
      "completed_at": 1727600600
    },
    "created_at": 1727600600
  }
]

Next steps