Skip to content

Quickstart

Create a payment link, share it, and get paid without writing an integration.

Create a link, share its address, and get told by webhook when someone pays, without writing an integration.

The type decides the shape of the page and which fields become mandatory. Four exist, and the choice is not cosmetic: a fundraising link renders a progress bar, an event link renders a date and a venue.

TypeWhat the payer seesWhat it requires
productA fixed price you setprice
eventA date, a venue, a ticket countevent_start_date, event_location, event_timezone
fundraisingA campaign with a goal and its progresstarget_amount
donationA free amount, or one you boundNothing beyond the basics

Beyond the type, every link needs a title of at least three characters, a currency, and a cta_type that sets the wording of the button: buy_now, pay, donate, register, or custom with your own custom_cta_text.

A product link also requires a product_type, one of physical, digital or service, which is what brings in stock, shipping or booking fields further down.

2. Create it

The Dashboard is the fastest route: Links → Create Link walks through the same fields with a preview beside them. What follows is the API equivalent, for when links are generated by your own software.

POSThttps://api.wajub.com/links
typeenumrequired
product, event, fundraising or donation.
product_typeenumoptional
Required for a product: physical, digital or service.
titlestringrequired
Shown as the heading on the page. Three characters minimum.
currencystringrequired
ISO 4217 code, the currency every payment on this link is collected in.
cta_typeenumrequired
Button wording: buy_now, pay, donate, register or custom.
pricenumberoptional
Required for a product. The fixed amount the payer is charged.
target_amountnumberoptional
Required for fundraising. The goal the progress bar fills toward.
min_amountnumberoptional
Floor for a free amount, on a donation.
max_amountnumberoptional
Ceiling for a free amount, on a donation.
custom_urlstringoptional
Your own slug in place of the generated one, letters, digits, dashes and underscores. Unique across Wajub, deleted links included: a slug another merchant, or a link you deleted, already holds answers 422 on custom_url.
external_referencestringoptional
Your own reference for the link, up to 255 characters. Returned as you sent it, never shown to payers.
descriptionstringoptional
Body text on the page, up to 5 000 characters.
callback_urlstring (url)optional
Where the payer is sent once the payment ends: after a success, and also when they cancel on the page. Read status in the query string.
expiry_datestring (date)optional
When the link stops accepting payments. Must be in the future.
sales_limitintegeroptional
How many units the link may sell. Each payment counts its quantity, and a payment still in progress holds its units until it succeeds or expires.
extra_fieldsarrayoptional
Questions asked on the page. Covered in the next step.
is_publicbooleanoptionaldefault : true
Whether the page opens at its URL. Send false to create the link offline: it answers 404 to payers until you publish it.

Send it with the secret key. Links are live only, so the key is sk. and not sk_test..

curl https://api.wajub.com/links \
  -H "Authorization: sk.xxxxxxxxxxxxxxxx…" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: link-cafe-amina" \
  -d '{
    "type": "product",
    "product_type": "physical",
    "title": "Café Amina, 250 g",
    "currency": "XAF",
    "price": 3500,
    "cta_type": "buy_now",
    "custom_url": "cafe-amina",
    "is_public": true
  }'

The answer is 201 Created, and url is what you came for: the address you share, built from your custom_url when you set one and from the generated id otherwise.

Response · 201 Created
{
"code": 201,
"status": "Created",
"message": "Payment link created",
"link": {
"id": "8kD2xQpLm",
"type": "product",
"product_type": "physical",
"title": "Café Amina, 250 g",
"currency": "XAF",
"price": 3500,
"cta_type": "buy_now",
"custom_url": "cafe-amina",
"url": "https://wajub.link/cafe-amina",
"qr_url": null,
"is_public": true,
"is_archived": false,
"tags": [
],
"created_at": "2026-09-11T14:00:00+00:00"
}
}

A link id is not prefixed like the rest of the API

Payments are trx_…, customers are cus_…, but a link id is a bare nine-character string such as 8kD2xQpLm, because it doubles as the URL slug. Set custom_url and it is your slug that lands in the address instead; the id stays what you pass to GET /links/{id}.

3. Ask the payer a question, if you need to

A table number, a T-shirt size, a delivery note: extra_fields adds inputs to the page and carries the answers through to the payment.

Two questions on the page
{
"extra_fields": [
{
"name": "table",
"label": "Table number",
"type": "text",
"required": true
},
{
"name": "note",
"label": "Note to the kitchen",
"type": "textarea",
"required": false
}
]
}

A field needs a name and a type: the name is the key its answer comes back under, and the type is the input the page draws. Customization lists the types and the other options.

What the payer types comes back on the payment, nested under its metadata rather than spread across it, so metadata.extra_fields.table is where the table number is. Read it there in your webhook handler and you have the answer next to the money.

4. Share it

The url works anywhere a URL works: a WhatsApp message, a bio, an SMS, a printed poster. Nothing needs to be embedded and there is no page of yours involved. For a poster, the Dashboard gives you the QR code on the link's own page, ready to download.

By default a link lives on wajub.link. On the Scale plan, a verified custom domain serves it instead, so the address the payer sees is yours.

On the page, the payer gets the operators available in their country, the same list the hosted checkout offers: MTN, Orange, Wave, Airtel, Moov, M-Pesa and cards where the country supports them. The full matrix is in Payment methods.

5. Get told when someone pays

A link produces ordinary payments. There is no link.paid event: what you listen for is payment.succeeded, exactly as with every other mode.

EventFires when
payment.succeededSomeone paid through the link. Fulfil on this one
link.createdA link was created
link.updatedA link was edited, archived or unarchived
link.deletedA link was deleted
link.expiredA link's expiry_date passed

Setting a callback_url sends the payer to your page afterwards, but that is a courtesy for them, not a signal for you. Treat the webhook as the source of truth, as always. Webhooks covers registration and signature verification.

Three different ways, depending on what you mean by closed.

You wantSet
It to stop on a dateexpiry_date
It to stop after N salessales_limit
It to stop after one sale, with one payer at a timesingle_use_link: true

A link can also be archived with is_archived through PUT /links/{id}, which takes it out of your lists without destroying what it has already collected. DELETE /links/{id} removes it outright.

In the Dashboard

Links is the whole product without the API. Creating, editing, archiving, plus three things the API does not expose: per-link analytics with views and conversion, the transactions each link produced, and its customers, exportable and reachable by bulk email.

The section needs the Payment Links feature on the plan and the view_links or manage_links permission, and it is hidden entirely while you are in sandbox.

What did you think of this content?