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.
Production only, and plan-gated
Payment links do not exist in the sandbox. Every link is live and takes real money from the first
share, so the thing you test with is a small real amount you refund afterwards. The feature also
needs the Payment Links feature on your plan: 25 links on Pay as you go, unlimited from Growth
upward. Past the cap, creation answers 403 with an upgrade message. A test key (sk_test.) gets
403 on every /links call, with This feature is only available in live mode.
1. Pick what the link is for
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.
| Type | What the payer sees | What it requires |
|---|---|---|
product | A fixed price you set | price |
event | A date, a venue, a ticket count | event_start_date, event_location, event_timezone |
fundraising | A campaign with a goal and its progress | target_amount |
donation | A free amount, or one you bound | Nothing 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.
https://api.wajub.com/linkstypeenumrequiredproduct, event, fundraising or donation.product_typeenumoptionalproduct: physical, digital or service.titlestringrequiredcurrencystringrequiredcta_typeenumrequiredbuy_now, pay, donate, register or custom.pricenumberoptionalproduct. The fixed amount the payer is charged.target_amountnumberoptionalfundraising. The goal the progress bar fills toward.min_amountnumberoptionaldonation.max_amountnumberoptionaldonation.custom_urlstringoptional422 on custom_url.external_referencestringoptionaldescriptionstringoptionalcallback_urlstring (url)optionalstatus in the query string.expiry_datestring (date)optionalsales_limitintegeroptionalextra_fieldsarrayoptionalis_publicbooleanoptionaldefault : truefalse 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.
A link goes live the moment you create it
is_public defaults to true, so the URL in the response takes real money as soon as you have
it. Send is_public: false to create the link offline instead: it then answers 404 This payment link is not available to everyone who opens it, until you publish it with PUT /links/{id} or
from the link's page in the Dashboard. The Dashboard creates links online too, and shows the same
flag as the link's Status.
The QR code comes from the Dashboard
qr_url can be null on a link created through the API. Download the QR code from the link's
page in the Dashboard, or render one yourself from url.
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.
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.
| Event | Fires when |
|---|---|
payment.succeeded | Someone paid through the link. Fulfil on this one |
link.created | A link was created |
link.updated | A link was edited, archived or unarchived |
link.deleted | A link was deleted |
link.expired | A 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.
Closing a link
Three different ways, depending on what you mean by closed.
| You want | Set |
|---|---|
| It to stop on a date | expiry_date |
| It to stop after N sales | sales_limit |
| It to stop after one sale, with one payer at a time | single_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.
Related pages
- Payment Links overviewWhat the product is, and when a link beats an integration.
- CustomizationBrand colours, logo, and the fields on the page.
- Tracking and conversionsViews, conversion rate, and the life of a link.
- Payment methodsWhich operators your payers see, country by country.
- WebhooksRegister an endpoint and verify the signature.
- Payment Links API referenceEvery field on the resource, and the five endpoints.