Customers can write in from Telegram, SMS, WhatsApp, Messenger and Instagram. Every conversation lands in the shared inbox, and agents reply from the same composer. You connect your own accounts with each provider.
Telegram
Setup
- In Telegram, talk to @BotFather: send
/newbotand pick a name and a username ending inbot. - Copy the bot token (
123456789:AA…). - In IsTyping, go to Settings → Channels → Connect Telegram, paste the token and click Connect. IsTyping then:
- calls
getMe; - calls
setWebhookwith a per-connection secret path and a randomsecret_token, andallowed_updates = message, edited_message, my_chat_member; - checks the
X-Telegram-Bot-Api-Secret-Tokenheader on every update (constant-time comparison).
- calls
- Share
https://t.me/<bot username>with customers.- Add
?start=<payload>(letters, digits,_or-, up to 64 characters) to learn where a customer came from. - The payload is stored on the visitor (
data.telegram_start) and shown in the details panel.
- Add
What the adapter supports
Chats
- Private chats only. Group, supergroup and channel updates, and messages from other bots, are acknowledged and ignored.
Inbound
- Text, photos (the largest size within 20 MB), documents, voice notes (stored as
audio/oggaudio files), audio, video and video notes. Files are downloaded withgetFileplus the file URL. - Stickers, locations, venues, contact cards, polls and dice become readable text, for example
[Sticker 😀]or📍 Location: 51.50070, -0.12460. - A contact card the user shares of themselves (
contact.user_id == from.id) gives the identity a provider-asserted phone number. Anyone else’s contact card does not.
Outbound
- Text is sent as plain text, with no
parse_mode, so nothing can be read as markup and nothing needs escaping. Text over 4096 characters is split. - Images are uploaded as photos (multipart, up to 10 MB). Other files are uploaded as documents (our upload limit of 10 MB applies).
- Operator typing sends
sendChatAction typing, throttled to once every 4 seconds.
Edits and deletes
- An operator edit of a delivered text message calls
editMessageText. A delete callsdeleteMessage, which Telegram only allows for messages under 48 hours old. - The outcome is shown under the message, for example “Edited here and on Telegram.”
- An
edited_messagefrom the customer is appended as a new message, “✏️ Edited an earlier message: …”. The messages feature only exposes edits through its own operator and visitor routes, so a provider edit never rewrites what operators already read.
Blocking and errors
my_chat_memberwith statuskickedmarks “blocked the bot”. Writing again clears it.- Sending: 429 is retried after
retry_after. 5xx and network errors are retried with backoff. - 403 is permanent and marks the bot as blocked. 401 and 404 mean the token was revoked; the connection shows “Needs attention”.
Not possible on Telegram
- Bots cannot start conversations; this is Telegram’s rule, and an operator start is refused with
cannot_initiate. - Telegram has no delivery or read receipts, so “Sent to Telegram” is the final state.
- Inline keyboards (chatbot buttons) are not used in this version.
Limits to expect
- About 1 message per second per chat, about 30 per second per bot overall, 20 per minute in groups.
- Bot downloads up to 20 MB; uploads up to 10 MB for photos and 50 MB for other files.
- Telegram keeps undelivered updates for up to 24 hours and retries a failing webhook “a reasonable amount” of times (the schedule is undocumented).
SMS (Twilio)
Setup
- In the Twilio console, note your Account SID and Auth token (Account → API keys & tokens).
- The auth token is always required, because Twilio signs webhooks with it.
- You can also enter an API key SID + secret. REST calls then use the key, which is recommended.
- Get an SMS-capable number. In the US, register A2P 10DLC or verify a toll-free number, otherwise carriers filter your messages (error 30007). A Messaging Service (MG…) works too.
- In IsTyping, go to Settings → Channels → Connect SMS and enter the SIDs, the token and the sender. Keep “Set
the webhook through the Twilio API” on:
- For a phone number, IsTyping sets the number’s
SmsUrl(POST). - For a Messaging Service, it sets
InboundRequestUrlandStatusCallback. “Use inbound webhook on number” must be off on its numbers.
- For a phone number, IsTyping sets the number’s
- If you prefer, paste the two URLs shown by hand:
- Incoming messages: Phone number → Messaging → “A message comes in”, Webhook, HTTP POST.
- Status callback: sent with every message automatically. For a Messaging Service, also set it as the service’s status callback.
What the adapter supports
Webhook security
X-Twilio-Signatureis checked exactly as Twilio documents it: HMAC-SHA1 of the full public URL plus every POST parameter, sorted by name, base64-encoded, compared in constant time.- Requests with no signature, a wrong signature, a tampered body or extra parameters, or a different URL get 403. An unknown path token gets 404.
- Replays are de-duplicated on
MessageSid.
Inbound
- Text and MMS media. Media URLs must be on the Twilio API host. They are fetched with Basic auth, and one redirect to Twilio’s short-lived URL is followed without credentials. Size is capped at 5 MB.
Outbound
- Text up to 1600 characters, which is Twilio’s cap. Longer text is refused, never truncated. The composer shows
characters / 1600 · n SMS(GSM-7 160/153, UCS-2 70/67, extension characters count double). - MMS images: JPEG, PNG or GIF, up to 5 MB.
- Each send carries
StatusCallback.
Delivery states
- Status callbacks drive sent → delivered and failed / undelivered. They are never applied backwards, because callbacks can arrive out of order.
- Common error codes are shown in plain words: 21610 opted out, 21211 invalid number, 21408 geo permissions, 21614 not a mobile number, 30003 unreachable handset, 30004 blocked, 30005 unknown handset, 30006 landline, 30007 carrier filtered, 30008 unknown.
Opt-out
- STOP, STOPALL, UNSUBSCRIBE, CANCEL, END, QUIT, REVOKE or OPTOUT (and Advanced Opt-Out’s
OptOutType=STOP) set “opted out” on the identity and record an event. - From then on every reply is refused before calling Twilio, with the reason “The customer opted out (STOP) …”, and the composer is blocked.
- START, UNSTOP, YES (or
OptOutType=START) opt the customer back in. - A 21610 from Twilio also sets the flag.
- HELP and INFO are passed through. Twilio answers them itself for long codes.
Costs and compliance (SMS)
- Every segment is billed, outbound and inbound. Long or Unicode messages cost more, so watch the counter.
- Consent. Only message people who agreed to it. Operators can start an SMS conversation only to a contact with
a phone number and a recorded consent (Contact → “Text messaging”: who recorded it, when and how). This is
audited in
channel_audit(and the tenant audit log) and rate-limited (30 per operator per hour, 300 per website per day). - Toll-fraud limits (Settings → Channels → “Starting SMS and WhatsApp conversations”, admin+; server-enforced,
modules/channels/outbound-policy.ts). A number that never messaged the website is a new number: only admins may record consent for it and start a conversation with it (agents still reply to, and may start with, numbers that wrote in). Operator-started SMS / WhatsApp only go to the destination countries — by default the countries of the website’s own sender numbers (+1 876 is Jamaica, not the US; calling codes without a country such as +881 are never allowed) — refused withcountry_not_allowedotherwise. At most 20 (configurable, hard maximum 200) operator-started conversations with new numbers per website per 24 h (daily_cap_reached). - Honour STOP. Twilio enforces it on long codes and toll-free numbers, and IsTyping refuses to send.
- Identify your business in first messages and keep HELP answers configured in Twilio.
WhatsApp (Twilio)
Setup
- Sandbox (testing). Twilio Console → Messaging → Try WhatsApp.
- Customers first send
join <code>to +1 415 523 8886. Sessions last 3 days, and only joined numbers can be messaged. - The sandbox has no custom templates (only Twilio’s pre-approved ones) and sends at most 1 message every 3 seconds.
- Production: register a WhatsApp Business sender (Meta Business verification, display name approval).
- Customers first send
- In IsTyping, go to Settings → Channels → Connect WhatsApp and enter the SIDs, the token and the sender number
(E.164, without
whatsapp:) or a Messaging Service SID. - Paste the Incoming messages URL into the sender’s (or Sandbox’s) “When a message comes in” field. The Twilio API can set it only for Messaging Service senders.
- Add your approved templates under the connection.
- Create them in Twilio’s Content Template Builder and get them approved.
- Then enter the Content SID (HX…), the approved text with
{{1}}placeholders, and a label for each placeholder. IsTyping does not create or approve templates.
What the adapter supports
Reply window
- The customer-service window is tracked from the customer’s last inbound message.
- Inside the 24 hours: free-form text and files (JPEG/PNG images, OGG/AMR/AAC/MPEG audio, MP4 video, PDF), up to 5 MB.
- Outside the window, or before the customer has written: the composer explains that free-form replies are not
allowed and offers Send a template.
- The template is sent as
ContentSidplusContentVariables, with no Body. - The inbox shows the rendered text.
- With no template configured, nothing can be sent, and the composer says so.
- The template is sent as
- A free-form message outside the window is refused before calling Twilio. A 63016 from Twilio (if the windows disagree) is shown as the same error.
Other behaviour
ProfileNamebecomes the visitor name. The sender number is carrier/WhatsApp-asserted.- Read receipts:
readcallbacks tick the message as read. - An operator edit or delete cannot be applied on WhatsApp. It is noted honestly: “Deleted here; already delivered to the customer (WhatsApp messages cannot be unsent).”
- Operator-initiated WhatsApp needs a phone number, recorded consent and a template.
Costs and compliance (WhatsApp)
- Meta charges per conversation or template category (marketing, utility, authentication, service) plus Twilio’s per-message fee.
- Template-initiated conversations are the expensive kind; replies inside the window are cheap or free, depending on current Meta pricing.
- Only message users who opted in to WhatsApp messages from you, and use the right template category. Misclassified marketing templates get re-categorised or rejected by Meta.
Messenger and Instagram (Meta)
Facebook Messenger and Instagram Direct come through one Meta app (a “Business” app on developers.facebook.com). The platform admin configures it once; website admins then connect their Facebook Page (and the Instagram professional account linked to it) with “Connect with Facebook”.
meta-service.ts (app settings, Facebook Login, webhook routing, token health, compliance), meta-routes.ts.
- Dashboard: Platform → Channels (
features/channels/meta.tsx→PlatformChannelsPage), Settings → Channels.
Verified only against a fake. No Meta app, Facebook Page or Instagram professional account was available when this was built. It follows Meta’s documentation as read on 2026-11-10 (sources in section 11) and is tested against
fake-meta.ts, which is only as faithful as those pages. Do the steps in “Testing for real” before customers rely on it.
5.1 Creating the Meta app (platform admin)
-
On developers.facebook.com → My Apps → Create app → type Business. Connect it to your business portfolio.
-
Add these products: Messenger, Instagram (the “Instagram API with Facebook Login” / Messenger API for Instagram setup), Facebook Login for Business (or Facebook Login), Webhooks.
-
In IsTyping, go to Platform → Channels and enter the App ID and App Secret (App settings → Basic). The secret is stored encrypted and is never shown again. Pick the Graph API version (default
v27.0, the current one; versions v21.0–v26.0 are still served). -
Paste the values IsTyping shows into the App Dashboard:
Value Where it goes Webhook callback URL https://<host>/api/v1/channels/meta/webhookWebhooks → Page object → Callback URL, and Webhooks → Instagram object → Callback URL (the same URL) Verify token (generated by IsTyping; “New verify token” rotates it) Webhooks → Verify token (both objects) OAuth redirect URI https://<host>/api/v1/channels/meta/oauth/callbackFacebook Login → Settings → Valid OAuth Redirect URIs (exactly; “Strict mode” on) Data deletion URL https://<host>/api/v1/channels/meta/data-deletionApp settings → Advanced / Data deletion → Data deletion callback URL Deauthorize URL https://<host>/api/v1/channels/meta/deauthorizeFacebook Login → Settings → Deauthorize callback URL -
Webhook fields:
- Page: IsTyping subscribes each connected Page itself (
POST /{page-id}/subscribed_appswithmessages, messaging_postbacks, messaging_referrals, message_deliveries, message_reads, message_echoes, message_reactions, message_edits). In the App Dashboard, subscribe the app’s Page object to the same fields. - Instagram: tick
messages, messaging_postbacks, messaging_seen, messaging_referral, message_reactionsby hand. The API cannot set Instagram fields.
- Page: IsTyping subscribes each connected Page itself (
-
Click Test the app. It makes an app-token call (
GET /{app-id}with{app-id}|{app-secret}). A wrong ID or secret gets a clear error. -
Only tick “This app has been approved for Human Agent” once Meta approved the Human Agent feature (see 5.5).
-
Until an app is configured, Messenger and Instagram are hidden for every website.
One app-level endpoint. Meta sends every Page’s events for an app to one callback URL. The receiver:
- checks
X-Hub-Signature-256against the app secret over the raw bytes; - routes each
entryby its id: the Page id for objectpage(Messenger), the Instagram account id for objectinstagram; - accepts an entry only if that connection uses the app that signed the body. Entries for unknown or disconnected Pages are acknowledged and dropped. Platform → Channels shows how many, and why.
A website’s own Meta app (optional, owner). Settings → Channels → “Your own Meta app”. Use it when a business must use its own app.
- The redirect URI and compliance URLs are the same. The webhook callback URL is the app’s OWN:
https://<host>/api/v1/channels/meta/webhook/<app config id>(shown in Settings → Channels). The receiver checks exactly that app’s secret over the raw body before it reads anything (security review: an unauthenticated POST costs one HMAC — no parsing, database access or queueing). Its webhooks only count for Pages connected with it, so a tenant’s app can never inject events for another app’s Pages. - Deployment note: a website app configured before this change with the shared
…/webhookURL must have its callback URL updated in the Meta App Dashboard (the shared URL only accepts the platform app now). - The GET handshake on each URL accepts only that app’s verify token.
- Replay protection: signed
signed_requestcallbacks (data deletion, deauthorize) are accepted once (a hash is kept for their one-hour validity,channel_meta_signed_requests); Meta events older than 29 days are refused; processed inbound queue rows are deleted after 30 days and never-processed ones after 90 (channels-retentionjob), and an already imported provider message is recognised bychannel_messages. Media downloads follow at most 3 redirects, each hop re-checked (a Meta media host whose addresses are public).
5.2 Connecting a Page / an Instagram account (website admin)
Prerequisites
- Messenger: a Facebook Page, and a Facebook account that can manage the Page’s messages (full control, or the “Messages” task).
- Instagram: an Instagram professional account (business or creator) linked to that Page. In the Instagram app: Settings → Messages and story replies → Message controls → Connected tools → Allow access to messages.
- Development mode: while the Meta app is in development mode, only people with a role on the app can log in and message the Page.
Connect with Facebook (Settings → Channels → Connect Messenger / Connect Instagram)
- IsTyping creates a single-use login flow. It is bound to the browser (cookie), the operator’s session and the
website, and sends the browser to
https://www.facebook.com/{version}/dialog/oauthwith:client_id;- the exact redirect URI built from
PUBLIC_URL; - a random
state; - only the permissions of the chosen channel(s).
- On the callback, IsTyping checks the state, the cookie and the session. It then exchanges the code for a short-lived user token and that for a long-lived user token, server-side with the app secret.
- IsTyping reads
/me/permissionsand/me/accounts: the Pages with their Page access tokens and linkedinstagram_business_account. - The admin picks a Page (within 30 minutes). IsTyping then:
- stores that Page token sealed;
- subscribes the app to the Page;
- records the Page id, Instagram id, names and pictures. The pictures are stored as our own files, because Meta’s URLs expire.
- The user tokens are never stored: reconnecting means logging in again. Page tokens obtained from a long-lived user token do not expire while the person’s grant stands.
- Redirects only ever go to
/w/<website>/settings/channels?..., built from the flow’s website id. There is no open redirect. - No PKCE: Meta documents PKCE only for its OIDC login flow (“in testing”,
openidscope), not for the Page permission flow.
Login failures are shown in plain words:
- the user cancelled or declined;
- the user manages no Pages;
- a forged or replayed state, or another browser;
- the user was signed out meanwhile;
- the exchange failed (wrong secret or redirect URI).
Picking a Page
- Missing permissions are listed in the picker.
- A Page without a linked Instagram account cannot connect Instagram.
- One Page → one website: a Page (or its Instagram account) already connected to another website on this install is refused.
- Connecting a Page that is already connected here reconnects it (new token, status back to active).
Paste a Page access token (developers)
- Useful in development mode with the Graph API Explorer. IsTyping checks the token with
debug_token: it must be valid, for this app, of typePAGE, carry the channel’s scopes, and not expire within a day.
Disconnect
- Deletes the token and unsubscribes the app from the Page, using the app token so it works with a dead Page token.
- When Messenger and Instagram of one Page are both connected, removing one keeps the subscription.
Token health
- What marks a connection “needs reconnect”:
- error 190 on any call;
debug_tokensaying invalid (a job checks every connection every 12 hours);- the Deauthorize callback.
- The connection card then shows a “Needs reconnect” banner with Reconnect with Facebook.
- Replies are refused before calling Meta, so there is no retry loop on a dead token. Incoming messages still arrive, because webhooks do not depend on the Page token.
5.3 What the adapters support
Identities and display
- One visitor per (website, kind, page-scoped id): PSID for Messenger, IGSID for Instagram.
verified: false. - Name, @username and picture come from the profile API:
- Messenger:
GET /{psid}?fields=first_name,last_name,profile_pic, which needs the “Business Asset User Profile Access” feature; - Instagram:
GET /{igsid}?fields=name,username,profile_pic, which needs the person’s consent (they messaged you).
- Messenger:
- Profiles are refreshed after 7 days. A refusal is remembered for a day and the visitor becomes “Messenger user 1234”.
- Meta gives no email or phone, so identities are never linked to a contact automatically. Operators can link one by hand.
- The details panel shows “Messenger · <Page>” / “Instagram · @handle”. It shows a profile link only if the API returns one (the profile APIs used here do not).
Inbound
- Text, images, video, audio, files (stored; at most 25 MB from Meta and our upload limit).
- Quick replies (text plus “(quick reply)”).
- Postbacks (“🔘 Tapped “Get started””).
- Referrals (m.me
?ref=and ads): “🔗 Came from a link (ref …)”, stored as the “Came from” value. - Edits are appended as “✏️ Edited an earlier message: …”. They never rewrite what operators read.
- Reactions (and their removal) become timeline events, not unread messages.
- Instagram stories:
- Story replies and mentions: the story media is downloaded at once (the CDN URL expires) and stored.
- When it is already gone: “(the story is no longer available)” / “Attachment not stored … the link expired”.
- Shares and reels become “🔗 Shared a post / reel”. Unsupported types become a placeholder.
- Instagram unsend (
is_deleted): the stored message is tombstoned through the messages feature’s delete semantics. Operators see “Message deleted” plus an event. An unsend processed before the message itself means the message is never shown. - Messenger documents no unsend webhook.
standby(another app owns the thread), handover and opt-in events are ignored safely.
Echoes (is_echo: messages the Page itself sent)
- Ours are dropped: echoes carrying our app id, or a message id we stored.
- Messages sent from the Page’s own inbox / Meta Business Suite / another tool appear once as an operator message by “Sent from Meta inbox”. They are never sent again.
Receipts
- Messenger
message_deliveries(mids or watermark) → delivered. message_reads(watermark) and Instagrammessaging_seen(read.mid) → read; the message reads as read.
Outbound
- Text: Messenger “less than 2000 characters”; Instagram 1000 bytes (UTF-8). Longer text is split into several messages.
- Messenger files are uploaded with the Attachment Upload API (multipart
filedata, thenattachment_id). Meta never fetches our URL. - Instagram files (JPEG/PNG images, MP4 video, AAC/MP4/MPEG/WAV audio, PDF) are sent by URL: our
public-but-unguessable
/files/<website>/<key>/<name>URL.- Meta’s fetcher must reach it over public HTTPS with a valid certificate and the right
Content-Type. - A CDN bot challenge (Cloudflare “Bot Fight”, JS challenge) in front of
/files/*makes these sends fail with 100/2018008 “could not download the file”.
- Meta’s fetcher must reach it over public HTTPS with a valid certificate and the right
- Images over 8 MB are refused (Meta’s image limit).
- Typing: the composer’s typing hook sends
typing_on, preceded once per new customer message bymark_seen.mark_seenis also sent before a reply. Opening a conversation alone does not mark it seen (there is no read hook in the framework). - Operator edits and deletes cannot be applied on Meta; this is noted under the message (“… cannot be unsent”).
5.4 Messaging windows and tags (as implemented)
| Since the customer’s last message | Rule | Composer |
|---|---|---|
| < 24 hours | messaging_type: RESPONSE |
“Messenger window open for 3 h 12 min” |
| 24 hours – 7 days, app approved for Human Agent (platform tick) | messaging_type: MESSAGE_TAG, tag: HUMAN_AGENT |
amber panel: “your reply goes out with the Human Agent tag … use it only for a person answering a support request”; hint “Human Agent tag · closes in 4 d 2 h” |
| 24 hours – 7 days, not approved | nothing is sent | blocked: “… the 7-day Human Agent tag is not enabled for this Meta app …” |
| > 7 days, or the customer never wrote | nothing is sent | blocked: “… does not allow replies now. The customer has to write again.” |
- Businesses cannot start Messenger / Instagram conversations (
cannot_initiate). - No other message tags are used. ACCOUNT_UPDATE, CONFIRMED_EVENT_UPDATE and POST_PURCHASE_UPDATE are deprecated (error 100 since 2026-04-27). Sending outside the rules risks the Page’s messaging.
- The window is checked again before every attempt. A Meta 10/2018278 (if the clocks disagree) is shown as “outside Messenger’s messaging window”.
Errors in plain words
| Meta error | What operators read | Effect |
|---|---|---|
| 190 | “Facebook no longer accepts this connection’s access … Reconnect” | needs reconnect, no retries |
| 10/2018278, 10/2534022 | outside the messaging window | not retried |
| 551 or */1545041 | “the customer can’t receive … right now (blocked the Page, restricted messages or deleted their account)” | composer blocked until they write again |
| 200/2534041 | the Instagram owner turned off “Allow access to messages” | connection error |
| 4, 17, 32, 613, 80006 | “rate limit — retrying in N s” (from X-Business-Use-Case-Usage) |
retried |
| 613/2018338 | tags flagged as abusive | not retried |
| 200 / 10 (others) | missing permission (before App Review only app-role users can be messaged) | not retried |
| 100/2018001, 100/2534014 | the person is not known any more | not retried |
| 100/2018008 | Meta could not download the file (CDN challenge / not public) | not retried |
| 1, 2, 5xx | temporary problem | retried |
5.5 Permissions, App Review and Business Verification
| Channel | Permissions (Advanced Access) |
|---|---|
| Messenger | pages_show_list, pages_manage_metadata, pages_messaging |
the above + pages_read_engagement, instagram_basic, instagram_manage_messages |
|
| Optional | business_management (Pages owned by a business portfolio) |
Features to request
- Human Agent, for 7-day replies. It needs App Review and Business Verification.
- Business Asset User Profile Access, for names and pictures.
Access levels
- Standard Access (no review) works only for people with a role on the app.
- Real customers need Advanced Access for each permission. That needs App Review, and Advanced Access needs Business Verification.
- Meta also requires the app to be Live (published) for webhooks from non-role users.
What the reviewer will test. Record a screencast per permission and give the reviewer a test account:
- Login flow (pages_show_list, pages_manage_metadata, instagram_basic, business_management): Settings → Channels → Connect Messenger → Connect with Facebook → the Facebook dialog → back in IsTyping, the Page picker lists the Page (with its Instagram account) → Connect.
- Receiving a message (pages_messaging, instagram_manage_messages): send a message to the Page from another Facebook account and a DM to the Instagram account. Show them arriving in the inbox.
- Sending a message (pages_messaging, instagram_manage_messages): reply from the inbox. Show the reply in Messenger / Instagram and the delivered/read tick.
- Human Agent: a conversation 24 hours – 7 days old. Show the amber “Human Agent tag” notice, and explain that only human support replies use it.
- Data deletion: show the callback URL and a confirmation page.
5.6 Compliance callbacks
Data deletion (POST /api/v1/channels/meta/data-deletion, form field signed_request)
- The signed request is
base64url(signature).base64url(payload), HMAC-SHA256 with the app secret. It is verified against the platform app and every website’s own app; a bad signature gets 403. - The payload’s
user_idis app-scoped: the Facebook user who removed the app, in practice the Page admin who logged in. IsTyping:- removes that id from connections and deletes their Page token, which that user granted (the connections then need a reconnect);
- erases, through the privacy registry (
eraseScope, delete mode), any Messenger / Instagram identity whose id equals it on Pages of that app, across all websites. Customers’ PSIDs/IGSIDs differ from app-scoped ids, so this normally finds nothing; customers ask the business directly.
- The answer is
{ url, confirmation_code }. GET /api/v1/channels/meta/data-deletion/<code>shows only the status and dates: no ids, names or Pages.
Deauthorize (POST /api/v1/channels/meta/deauthorize)
- Uses the same signed request. The Pages connected by that user show “needs reconnect”.
5.7 Development-mode testing (app roles)
- Keep the app in development mode. Add yourself and testers under App roles. Only they can log in and message.
- Configure the app in Platform → Channels and click Test the app.
- In the App Dashboard, verify both webhook objects: Meta calls the GET handshake, and the verify token must match.
- Connect your test Page in Settings → Channels.
- From a tester account, message the Page and DM the Instagram account. Reply from the inbox.
- App Dashboard → Webhooks → “Test” sends sample events. They name Page
0/ random ids, so IsTyping acknowledges and drops them (visible in Platform → Channels).
5.8 Common failures
- Webhook verification fails in the App Dashboard: wrong verify token, wrong
PUBLIC_URL, or a CDN challenge on/api/v1/channels/*. - Nothing arrives:
- the Page is not subscribed (connection card → Re-subscribe webhooks);
- Instagram fields are not ticked in the App Dashboard;
- “Allow access to messages” is off in Instagram;
- the app is in development mode and the sender has no role;
- Meta disabled the webhook after an hour of failures (check the App Dashboard alerts and re-subscribe).
- “invalid signature” in the log: the App Secret in IsTyping does not belong to the app that sends the webhook.
- Login says “exchange failed”: wrong App Secret, or the redirect URI in Facebook Login settings is not exactly the one shown.
- Replies fail with “missing a permission”: before App Review only app-role users can be messaged.
- Instagram files fail with “could not download the file”:
/files/*is behind a bot challenge, or not public HTTPS. - A CDN or bot challenge must not sit in front of
/api/v1/channels/*(webhook + compliance callbacks),/api/v1/channels/meta/oauth/*(the browser comes back from Facebook) and/files/*(Instagram media, pictures).
Identities, contacts and conversations
Identities
- One visitor per (website, channel kind, external user id): Telegram user id, the E.164 number for SMS and WhatsApp, the Page-scoped id (PSID / IGSID) for Messenger and Instagram.
- It has no browser session and
verified: false, and the provider’s profile name, username and phone.
Contacts
- An identity is linked to a Contact only through a phone number the provider asserts: the SMS/WhatsApp sender, or a Telegram contact the user shared of themselves.
- The link happens only when exactly one contact has that number (digits compared). Otherwise an operator links it by hand in the details panel (“Link to contact…”, audited).
- Linking never enriches or changes the contact, and never sets the visitor’s email. Unread-reply emails therefore never go to channel customers.
Conversations
- Identities are never merged into a browser visitor’s chat or an email identity, even with the same phone number.
- One conversation per identity. It is created
pendingon the first message and reopened (with areopenedevent) when resolved. Conversation.channelConnectionsays which bot or number the conversation came through, and replies go out through it.- If the same person writes to two numbers of the website, the latest one wins.
What the details panel shows
- The identity: formatted number or @username, and an “Unverified” or “Number from the provider” badge with an explanation.
- Opt-out and blocked-bot state, the start payload, and the linked contact.