Search for a command to run...
Onboard your clients' WhatsApp Business numbers through an Intelli-hosted flow — you never touch Meta credentials.
You only ever need your API key.
Intelli hosts the Meta signup popup on its own domain. You don't register a Meta app, you don't handle a Meta App ID, Config ID, or access token, and no Meta credential ever reaches your frontend. Your server holds a single secret: your ik_ key.
Copy the integration contract into your assistant, then let it tailor the implementation to your stack.
You are helping me integrate WhatsApp into my product using the Intelli Partner API (base URL: https://api.intelliconcierge.dev/v1). Intelli hosts Meta's embedded signup, so my app never touches Meta credentials — everything goes through my Intelli API key.
Before writing code, ask me:
1. My backend framework/language (and frontend framework if relevant).
2. Whether the hosted signup should open as a popup or a full redirect.
Environment variables (server-side only — the API key must NEVER reach the browser):
- INTELLI_API_KEY: my secret key. ik_test_... keys simulate every send except to my registered test recipients (my own phone/Instagram), which receive the real message; ik_live_... delivers for real.
- INTELLI_WEBHOOK_SECRET: HMAC secret for verifying incoming webhooks (from the Intelli portal, Webhooks section).
Every API call: Authorization: Bearer <INTELLI_API_KEY>. Errors are JSON {"error": {"code": "...", "message": "...", "details": {...}}}.
Implement:
A) Client onboarding (Intelli-hosted embedded signup)
1. A server endpoint that creates a signup session:
POST /embedded-signup/sessions with body {"client_ref": "<my unique id for this customer>"}
-> 201 {"session_id": "...", "url": "https://...", "expires_in": 600, "reconnect": false}
Sessions are single-use, valid 10 minutes. Reusing the client_ref of an already-connected client is a reconnect ("reconnect": true): the same number goes through signup again and gets a fresh connection — this is how a client recovers from an expired or revoked connection, or one that was issued without template permission (error codes client_token_invalid, template_permission_missing, waba_mismatch, waba_inaccessible). Handle 409 "number_already_connected" (the number is connected under another of my client_refs — the message names it — or through another provider) and 409 "client_number_mismatch" (a reconnect that connected a different number than the client's).
2. Frontend: open the returned url in a popup (about 600x720). Listen for a window "message" event where event.data = {type: "intelli:whatsapp", status: "success", client_ref: "...", reconnected: false}. Treat it as a hint only — it is untrusted.
3. Server-side confirmation: GET /clients/<client_ref>
-> 200 {client_ref, channel, waba_id, phone_number_id, phone_number, business_name}
Only mark the customer as connected once this succeeds. Persist client_ref — it identifies this customer in every later call.
Offboarding: DELETE /clients/<client_ref> disconnects the customer (immediate and final — sends and webhooks stop, delivery logs are removed; the number can be connected again later under any client_ref). Prefer a reconnect (same client_ref through signup) when only the connection is broken.
4. Instagram variant: POST /instagram-connect/sessions works identically (same body, same 3 steps; completion message type is "intelli:instagram"). The resulting client has channel "instagram": "to" is the Instagram-scoped user ID from webhook events, and it carries text plus image/video/audio/document — media must be a public HTTPS URL in "link" (an uploaded media "id" returns "media_link_required"), and "caption"/"filename" come back under "warnings" because an Instagram attachment has nowhere to put them. Templates, sticker, location, contacts, interactive and reaction are WhatsApp-only and return "channel_type_unsupported".
B) Sending messages (server-side)
- POST /messages/send mirrors Meta's Cloud API: {"client_ref": "...", "to": "<E.164 digits, no +>", "type": "<t>", "<t>": {...}} where <t> is one of text, image, video, audio, document, sticker, location, contacts, interactive, reaction. Examples: text needs {"body": "..."}; media types need {"link": "<public https url>"} (or an uploaded media "id"), image/video/document accept "caption", document accepts "filename"; location needs {latitude, longitude}; interactive needs Meta's interactive object ({"type": "button" | "list" | ...}); reaction needs {message_id, emoji} (empty emoji removes it).
- Template: POST /messages/send-template with {"client_ref": "...", "to": "...", "template": {"name": "...", "language": {"code": "en_US"}, "components": [...]}}
- Free-form types require an open 24h customer-service window; outside it only templates deliver.
- Success: {"success": true, "message_id": "wamid..."}. With a test key the response includes "dry_run": true and nothing is delivered.
C) Webhook receiver
- A public POST endpoint in my app; I register its URL in the Intelli portal (Webhooks section).
- Verify the X-Intelli-Signature header: "sha256=" + hex(HMAC_SHA256(INTELLI_WEBHOOK_SECRET, raw request body)), constant-time comparison, reject mismatches.
- Payload: {"event": "...", "timestamp": "...", "client_ref": "...", "phone_number": "...", ...}. Event types: message.received (has messages[]), message.status (has statuses[]), message.reaction, template.status, user.preferences.
- Respond 2xx immediately and process asynchronously; failed deliveries are retried with backoff, and events can be redelivered manually — dedupe by message id.
D) Error handling
- 401 invalid/revoked key, 403 missing scope, 404 unknown client_ref, 409 number_already_connected / client_number_mismatch, 429 rate-limited (respect Retry-After), 402 subscription required.
Start with my ik_test_ key end-to-end, then switch to live. Write idiomatic, production-quality code for my stack.Call from your server with your API key. client_ref is your own identifier for this client (unique within your account). Requires the clients:write scope.
POST https://api.intelliconcierge.dev/v1/embedded-signup/sessions
Authorization: Bearer ik_live_...
Content-Type: application/json
{
"client_ref": "your-internal-client-id"
}{
"session_id": "a7jBeKh4uW97sypQ...",
"url": "https://www.intelliconcierge.dev/embed/whatsapp?session=a7jBeKh4uW97sypQ...",
"expires_in": 600,
"reconnect": false
}The session is single-use and expires in 10 minutes. reconnect is true when the client_ref already belongs to one of your connected clients — see reconnecting a client.
Open url in a popup (recommended) or redirect your client to it. When opened as a popup, the page posts a message back to the opener when the flow completes:
const popup = window.open(session.url, "intelli-whatsapp", "width=600,height=720");
window.addEventListener("message", (event) => {
if (event.data?.type === "intelli:whatsapp" && event.data?.status === "success") {
// The number is connected. Confirm it from your server (step 3).
finishOnboarding(event.data.client_ref);
}
});The message only signals completion — treat it as untrusted and always confirm the result server-to-server in step 3.
Fetch the onboarded client from your server with your API key. Requires the clients:read scope.
GET https://api.intelliconcierge.dev/v1/clients/your-internal-client-id Authorization: Bearer ik_live_...
{
"client_ref": "your-internal-client-id",
"waba_id": "123456789",
"phone_number_id": "987654321",
"phone_number": "+1234567890",
"business_name": "Client Business"
}Store the client_ref — you'll use it to send messages and to match incoming webhooks for this client.
Meta bills each client directly for WhatsApp conversations and rejects template messages until a card is on file in the Meta Business Portfolio that owns the waba_id. The client's page in this dashboard has an Add a payment method in Meta link that opens their payment settings — send it to them once signup completes.
A client's connection is the token Meta issued when its number went through signup. It can expire, be revoked from the client's Meta Business Portfolio, or have been issued without a permission the client now needs — the template endpoints answer client_token_invalid, waba_mismatch, waba_inaccessible, template_permission_missing or whatsapp_permissions_missing when that is the case. The fix is always a fresh connection, and that is a signup session created with the same client_ref:
POST https://api.intelliconcierge.dev/v1/embedded-signup/sessions
Authorization: Bearer ik_live_...
Content-Type: application/json
{
"client_ref": "your-internal-client-id"
}
→ 201 { "session_id": "...", "url": "...", "expires_in": 600, "reconnect": true }Open the URL for the client exactly as in step 2. The client must connect its own number: the client keeps its client_ref, webhooks and history, and only the connection is replaced. The completion message carries reconnected: true, and the client's record is refreshed with whatever Meta reports now (waba_id, phone_number_id). If a different number goes through the popup, the signup is discarded and the client is left as it was (409 client_number_mismatch) — onboard a new number under a new client_ref instead.
When a connection comes back without permission to manage templates, the hosted page tells the person who connected, and the direct POST /clients/signup response lists it under client.warnings (code: template_permission_missing). Sending works regardless; template creation will not until the client reconnects with that permission granted.
The same three-step contract connects an Instagram professional account. Create the session on a different endpoint; everything else — opening the URL, the completion message (type intelli:instagram), and the server-side confirmation via GET /clients/<client_ref> — works identically.
POST https://api.intelliconcierge.dev/v1/instagram-connect/sessions
Authorization: Bearer ik_live_...
Content-Type: application/json
{
"client_ref": "your-internal-client-id"
}Connected clients carry channel: "instagram" in the clients API, webhook events include the same field, and sends route automatically. Instagram carries text plus image, video, audio and document — media as a public HTTPS URL in link, since Instagram fetches the file itself. Templates are WhatsApp-only.
Copy the integration contract into your assistant, then let it tailor the implementation to your stack.
You are helping me integrate Instagram DMs into my product using the Intelli Partner API (base URL: https://api.intelliconcierge.dev/v1). Intelli hosts the Instagram connect flow, so my app never touches Meta credentials — everything goes through my Intelli API key.
Before writing code, ask me:
1. My backend framework/language (and frontend framework if relevant).
2. Whether the hosted connect should open as a popup or a full redirect.
Environment variables (server-side only — the API key must NEVER reach the browser):
- INTELLI_API_KEY: my secret key. ik_test_... keys simulate every send except to my registered test recipients (my own phone/Instagram), which receive the real message; ik_live_... delivers for real.
- INTELLI_WEBHOOK_SECRET: HMAC secret for verifying incoming webhooks (from the Intelli portal, Webhooks section).
Every API call: Authorization: Bearer <INTELLI_API_KEY>. Errors are JSON {"error": {"code": "...", "message": "...", "details": {...}}}.
Implement:
A) Client onboarding (Intelli-hosted Instagram connect)
1. A server endpoint that creates a connect session:
POST /instagram-connect/sessions with body {"client_ref": "<my unique id for this customer>"}
-> 201 {"session_id": "...", "url": "https://...", "expires_in": 600}
Sessions are single-use, valid 10 minutes. Handle 409 code "account_already_connected": that Instagram account is already connected (as one of my clients, or through another provider) — show the customer a clear message.
2. Frontend: open the returned url in a popup (about 600x720). The page walks the customer through Instagram's login/consent, then posts a window "message" event where event.data = {type: "intelli:instagram", status: "success", client_ref: "..."}. Treat it as a hint only — it is untrusted.
3. Server-side confirmation: GET /clients/<client_ref>
-> 200 {client_ref, channel: "instagram", business_name, ...}
Only mark the customer as connected once this succeeds. Persist client_ref.
B) Sending messages (server-side)
- POST /messages/send with {"client_ref": "...", "to": "<Instagram-scoped user ID>", "type": "text", "text": {"body": "..."}}
- "to" is the IGSID that arrives in webhook events (sender.id) — you cannot derive it from a username. Text is capped at 1000 characters.
- Media: types "image", "video", "audio" and "document" also work. Pass a public HTTPS URL, e.g. {"type": "image", "image": {"link": "https://..."}}. Instagram fetches the file itself, so an uploaded media "id" is rejected with code "media_link_required". A "caption" or "filename" has nowhere to go on an Instagram attachment: the send succeeds and the response lists it under "warnings" — send the caption as its own text message if you need it.
- Not available on Instagram (code "channel_type_unsupported"): templates, sticker, location, contacts, interactive (buttons/lists) and reaction.
- Instagram allows replies within 24 hours of the customer's last message — send outside that window and Meta rejects the call.
- Success: {"success": true, "message_id": "..."}, plus "warnings": [...] when a field could not be carried. With a test key the response includes "dry_run": true and nothing is delivered — a test key rejects exactly what a live key would.
C) Webhook receiver
- A public POST endpoint in my app; I register its URL in the Intelli portal (Webhooks section).
- Verify the X-Intelli-Signature header: "sha256=" + hex(HMAC_SHA256(INTELLI_WEBHOOK_SECRET, raw request body)), constant-time comparison, reject mismatches.
- Payload: {"event": "...", "channel": "instagram", "timestamp": "...", "client_ref": "...", ...}. Event types: message.received (has messages[] — each with sender.id = the IGSID to reply to), message.echo, message.reaction, message.read, message.postback.
- Respond 2xx immediately and process asynchronously; failed deliveries are retried with backoff, and events can be redelivered manually — dedupe by message id.
D) Error handling
- 401 invalid/revoked key, 403 missing scope, 404 unknown client_ref, 409 account_already_connected, 429 rate-limited (respect Retry-After), 402 subscription required, 400 channel_type_unsupported (type the channel does not carry), 400 media_link_required (media sent as an id instead of a URL).
Start with my ik_test_ key end-to-end, then switch to live. Write idiomatic, production-quality code for my stack.With a connected client you can send messages and receive webhooks on their behalf.