Setup Links
A Setup Link is a one-time URL you give your customer. They click it, log into their Facebook Business account, authorize your app, and a WABA + phone numbers get provisioned automatically — no manual config on your side.
This is the recommended onboarding flow for any multi-tenant SaaS.
You ──► generate Setup Link ──► send to customer
│
â–Ľ
Customer clicks ──► Meta's Embedded Signup
│
â–Ľ
Customer authorizes your app, picks/verifies phone
│
â–Ľ
We receive the OAuth callback ──► provision WABA + phones
│
â–Ľ
Webhook to you ──► `whatsapp.phone_number.created`Generating a setup link
1. Create the customer first
A setup link is associated with a Customer (so we know who’s onboarding).
POST /admin/tenants/{tenantId}/customers
{
"name": "ClĂnica San Lucas",
"external_id": "clinic_001"
}2. Create the setup link
POST /admin/customers/{customerId}/setup-links
{
"successRedirectUrl": "https://your-app.com/onboarding/success",
"failureRedirectUrl": "https://your-app.com/onboarding/failed",
"expiresAt": "2026-05-25T23:59:59Z"
}Response:
{
"id": "abc123-...",
"url": "https://cloud.gosendapi.com/signup/abc123-...",
"status": "pending",
"expiresAt": "2026-05-25T23:59:59Z",
"successRedirectUrl": "https://your-app.com/onboarding/success",
"failureRedirectUrl": "https://your-app.com/onboarding/failed"
}3. Send the URL to your customer
Embed it in your UI, email it, SMS it — however you onboard customers. The URL is one-time-use only.
What the customer sees
When they open the URL:
- Landing page with your project name and a “Continue with Facebook” button
- Meta’s Embedded Signup popup — they choose:
- Which Business Manager (or create one)
- Which phone number to register (or buy a new one via Meta)
- Which display name to use
- PIN verification for the phone (SMS or call)
- Permissions screen — they authorize your app to send/receive on the WABA
- Redirect to your
successRedirectUrlwith?status=success&waba_id=...
The whole flow takes 2-5 minutes if they already have a Facebook Business account, 15-30 minutes if creating from scratch.
What happens server-side
After the customer completes the flow:
- Meta calls our OAuth callback
- We exchange the auth code for a long-lived System User access token
- We encrypt and store the token
- We fetch the phone number metadata from Meta
- We persist the WABA + phone(s) linked to the Customer
- We fire webhooks to your endpoint:
whatsapp.phone_number.created— with the newphone_number_id
- We redirect the customer’s browser to your
successRedirectUrl
By the time the customer lands back on your app, the WABA is ready to send/receive.
Setup link properties
| Field | Type | Description |
|---|---|---|
id | uuid | The link identifier |
url | string | Full URL to share with the customer |
status | enum | pending / completed / failed / expired |
tenantId | bigint | Your project |
customerId | bigint | Which customer is onboarding |
expiresAt | datetime | When the link stops working (default 7 days) |
usedAt | datetime | When the customer clicked (null if pending) |
resultingWabaId | bigint | The created WABA (null until completed) |
successRedirectUrl | string | Where to send the user after success |
failureRedirectUrl | string | Where to send the user after failure |
allowedConnectionTypes | array | Restrict modes: ["dedicated"], ["coexistence"], or both |
themeConfig | object | Custom colors/logo for the landing page |
phoneProvisioningEnabled | bool | Allow the customer to buy a new number via Meta |
phoneNumberCountryIsos | array | If buying new number, which countries: ["US", "AR"] |
Theming the landing page
You can pass a themeConfig to make the landing match your brand:
{
"themeConfig": {
"primaryColor": "#10B981",
"backgroundImage": "https://your-cdn.com/onboarding-bg.jpg",
"logoUrl": "https://your-cdn.com/logo.png",
"companyName": "Acme SaaS"
}
}Reconnecting a number
If a customer’s access token expires (revoked password, deauthorized our app, etc.), the phone goes into disconnected status. Generate a reconnect setup link to walk them through reauthorization without re-creating the WABA:
POST /admin/customers/{customerId}/setup-links
{
"reconnectPhoneNumberId": "<internal_phone_id>"
}The flow is identical from the customer’s perspective, but the existing WABA’s token is refreshed (no new WABA created).
Best practices
- Set
successRedirectUrlto a page that polls for the newphone_number_iduntil the webhook arrives. Sometimes the redirect happens before our DB has the WABA persisted (rare, but possible). - Show a “what to expect” page before the link. Customers panic when they see Facebook OAuth — explain what’s about to happen.
- Pre-populate the customer’s email in your CRM with the link, so they have it for later if they close the browser mid-flow.
- Add a timeout to
expiresAtof 7-30 days. Shorter = more secure but more friction if the customer takes time to act.
Common issues
| Issue | Cause | Fix |
|---|---|---|
| Link returns “expired” | expiresAt passed | Generate a new link |
| Customer sees “no Business Manager” | They don’t have one yet | They need to create one at business.facebook.com first. Meta surfaces this in the embedded flow. |
| Webhook never arrives | Webhook URL misconfigured | Check delivery logs in dashboard |
usedAt is set but resultingWabaId is null | Flow started but customer abandoned mid-way | Generate new link, they have to start over |
Coming next
- White-label landing: full-page custom domain like
signup.yourcompany.com - API key bootstrap: customer immediately gets a per-WABA token to use in their own systems
- Multi-step onboarding: pre-collect info before Meta flow, post-survey after
See Customers concept for the full lifecycle.