Channels
Where chat is exposed: the embeddable widget, its public configuration, and WhatsApp.
11 endpoints. 5 are reachable without a dashboard session — they are listed in the Integration API — and the other 6 require a Cognito JWT. The badge under each path says which.
/widget.js Embed widget bundle (301 to CDN when configured)
Auth Public — no credential
Path parameters
None.
Query parameters
None.Serves a fixed document and takes no input at all.
Body parameters
None.GET requests carry no body.
Response
Status codes
| Status | Meaning |
|---|---|
| 200 | Success. |
| 429 | Rate limited. See rate limits. |
curl
curl -X GET 'https://api.oprag.ai/widget.js' \
-H 'X-Oprag-Key: sk_live_...' /v1/widget/{projectId}/config Public display configuration for a project's chat widget. Optional fields — quickPrompts, avatarUrl, launcherIconUrl, ctaChips, proactiveTriggers, and lead-capture settings — appear only when set; visitorContextEnabled is a master consent switch.
Auth Public — no credential
Before you call it
- Only served while the project is live or indexing and its widget is enabled.
- Returns
displayName,welcomeMessage, andtheme(primaryColor,position, and an optionalmode). theme.modeisauto,light, ordark.autofollows the visitor'sprefers-color-scheme.quickPromptsand the lead-capture fields appear only when those features are on,avatarUrl,launcherIconUrl,ctaChips, andproactiveTriggersonly when set on the widget config, andescalationEnabledonly when the project has an escalation webhook or email.proactiveTriggersdrive the widget's teaser bubbles. The widget shows at most one teaser per session, and suppresses all teasers for 7 days after a visitor dismisses one.visitorContextEnabledis a project-level master switch, distinct from the widget's per-visitor consent banner (window.oprag.setConsent()/handle.setConsent()). Omitted ortrue(the default) leaves visitor-context capture and the consent banner on;falsedisables both for every visitor on the project.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
projectId | string | Required | Project the request applies to. Must belong to the calling workspace. |
Query parameters
None.Reads the resource named in the path; there is nothing else to select.
Body parameters
None.GET requests carry no body.
Response
200 Success
{
"displayName": "Acme Support",
"welcomeMessage": "Hi! Ask me anything about our product.",
"quickPrompts": ["What is your refund policy?"],
"avatarUrl": "https://cdn.acme.com/bot-avatar.png",
"launcherIconUrl": "https://cdn.acme.com/launcher-icon.svg",
"ctaChips": [
{ "label": "Book a demo", "url": "https://acme.com/demo" },
{ "label": "Email us", "url": "mailto:hello@acme.com" }
],
"proactiveTriggers": [
{
"message": "Questions about pricing? I can help!",
"trigger": "delay",
"value": 30,
"pagePattern": "/pricing*"
}
],
"feedbackEnabled": true,
"visitorContextEnabled": true,
"escalationEnabled": false,
"theme": {
"primaryColor": "#2563eb",
"position": "bottom-right",
"mode": "auto"
}
} Status codes
| Status | Meaning |
|---|---|
| 200 | Success. |
| 404 | No such resource in this workspace. |
| 429 | Rate limited. See rate limits. |
curl
curl -X GET 'https://api.oprag.ai/v1/widget/{projectId}/config' \
-H 'X-Oprag-Key: sk_live_...' SDK equivalent `mountWidget()` reads this
/v1/widget/{projectId}/conversations/{conversationId}/messages Restore a returning visitor's transcript, using the conversationId and visitorId the widget saved.
Auth Embed keyIntegration key Embed keys require a browser `Origin` on the project's allowlist
Before you call it
- This is what powers the widget's transcript restore: the widget persists
conversationIdandvisitorIdlocally and replays the thread when the visitor comes back. messagesis chronological (oldest first), capped at the most recent 50.- Each message carries
role(userorassistant),text, andcreatedAt.sourcesappears only when the stored answer cited documents, and each entry carriesdocumentTitleonly — no excerpts, locations, or scores on this channel. - The 404 is deliberately indistinguishable: a missing conversation, a conversation from another project, and a
visitorIdmismatch all answer identically, so conversation ids cannot be probed.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
projectId | string | Required | Project the request applies to. Must belong to the calling workspace. |
conversationId | string | Required | Conversation within the project. |
Query parameters
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
visitorId | string | Required | — | The visitor the widget saved alongside the conversationId. A missing or blank value is a 400; a mismatch is the same 404 as an unknown conversation. |
Body parameters
None.GET requests carry no body.
Response
200 Success
{
"messages": [
{
"role": "user",
"text": "What is your refund policy?",
"createdAt": "2026-08-27T09:14:03.000Z"
},
{
"role": "assistant",
"text": "Refunds are available within 30 days of purchase.",
"createdAt": "2026-08-27T09:14:07.000Z",
"sources": [{ "documentTitle": "Refund policy" }]
}
]
} Status codes
| Status | Meaning |
|---|---|
| 200 | Success. |
| 400 | visitorId is required — the query parameter is missing or blank. |
| 401 | Missing, revoked, or wrong-environment key. |
| 403 | No Origin header, an origin that is not allowlisted, or a blocked IP. |
| 404 | Conversation not found — an unknown conversation, another project's conversation, or a visitorId that does not match. All three are indistinguishable by design. |
| 429 | Rate limited. See rate limits. |
| 503 | Project is not live yet. Wait for indexing to complete before chatting. |
curl
curl -X GET 'https://api.oprag.ai/v1/widget/{projectId}/conversations/{conversationId}/messages' \
-H 'X-Oprag-Key: sk_live_...' /v1/webhooks/whatsapp Answers Meta's hub subscription challenge when the webhook is registered.
Auth Integration key
Before you call it
- Meta calls this when you register the webhook; your code never does.
hub.verify_tokenis compared against the globalWHATSAPP_VERIFY_TOKEN, never a per-project value.
Path parameters
None.
Query parameters
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
hub.mode | "subscribe" | Required | — | Meta sends subscribe. Any other value is rejected with 403. |
hub.verify_token | string | Required | — | Compared in constant time against the configured verify token. A mismatch is a 403. |
hub.challenge | string | Required | — | Echoed back verbatim as the plain-text 200 body when verification passes. |
Body parameters
None.GET requests carry no body.
Response
200 Success
1234567890 Status codes
| Status | Meaning |
|---|---|
| 200 | Verified. The body is hub.challenge verbatim, as plain text. |
| 401 | Missing, revoked, or wrong-environment key. |
| 403 | Invalid verification request when a parameter is missing or hub.mode is not subscribe; Verification token mismatch when the token does not match. |
| 429 | Rate limited. See rate limits. |
curl
curl -X GET 'https://api.oprag.ai/v1/webhooks/whatsapp' \
-H 'X-Oprag-Key: sk_live_...' /v1/webhooks/whatsapp Receives inbound WhatsApp messages from Meta and answers them through the project's chat pipeline.
Auth Integration key
Before you call it
- Meta calls this, not your code. It needs an
X-Hub-Signature-256header —sha256=followed by an HMAC-SHA256 of the raw body under the app secret — and the raw JSON body. - Text messages only. Deliveries are de-duplicated on Meta's message id, and
phone_number_idselects which project answers. - Answers run through the shared chat pipeline with
channel: "whatsapp". - An unmapped or disabled connection is acknowledged without running chat at all, so Meta stops retrying.
Path parameters
None.
Query parameters
None.The provider signs and posts the whole payload; nothing is read from the URL.
Body parameters
Defined by the caller.Inbound message payload from WhatsApp.
Response
Status codes
| Status | Meaning |
|---|---|
| 200 | Acknowledged, as plain-text OK. Meta retries anything else, so this is the answer whenever the signature is valid. |
| 401 | Invalid webhook signature — the HMAC did not match. |
| 403 | Origin or IP not allowed for this project. |
| 429 | Rate limited. See rate limits. |
| 503 | WHATSAPP_APP_SECRET is not configured. |
curl
curl -X POST 'https://api.oprag.ai/v1/webhooks/whatsapp' \
-H 'X-Oprag-Key: sk_live_...' /v1/projects/{projectId}/widget-config Widget settings (dashboard)
Auth Cognito JWT member
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
projectId | string | Required | Project the request applies to. Must belong to the calling workspace. |
Query parameters
None.Reads the resource named in the path; there is nothing else to select.
Body parameters
None.GET requests carry no body.
Response
Status codes
curl
curl -X GET 'https://api.oprag.ai/v1/projects/{projectId}/widget-config' \
-H 'Authorization: Bearer <Cognito JWT>' /v1/projects/{projectId}/widget-config Update the widget's display, branding (avatar, launcher icon, CTA chips), proactive triggers, lead capture, and theme settings.
Auth Cognito JWT admin
Before you call it
- Branding URLs —
avatarUrl,launcherIconUrl, and each chip'surl— must behttps://, up to 2048 characters; chip URLs may also bemailto:. URLs carrying credentials, a raw IP address, or a localhost host are rejected with 400. ctaChipslabels are limited to 40 characters. At most 3 chips are kept, at most 3proactiveTriggers, and at most 6quickPrompts; overflow is dropped silently, not rejected.- Each proactive trigger fires its
messageas a teaser on the visitor's page.trigger: "delay"needsvaluein whole seconds (5–600),trigger: "scroll"needsvaluein whole percent (10–90), andtrigger: "exit"(exit intent) must omitvalue. An optionalpagePattern— starting with/, up to 200 characters, with one optional trailing*wildcard — limits which pages it fires on. - An empty string clears
avatarUrlorlauncherIconUrl; an empty array clearsctaChips,quickPrompts, orproactiveTriggers. feedbackEnabledandvisitorContextEnabledboth default totruewhen omitted.visitorContextEnabled: falseis a project-level kill switch: it turns off visitor-context capture (referrer, landing page, UTM) and the widget's per-visitor consent banner for every visitor, overriding any visitor's own consent choice — seeGET /v1/widget/{projectId}/config.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
projectId | string | Required | Project the request applies to. Must belong to the calling workspace. |
Query parameters
None.Everything it needs is in the request body.
Body parameters
| Name | Type | Required | Description |
|---|---|---|---|
enabled | boolean | Optional | Whether the feature is active. |
displayName | string | Optional | See the request example below. |
welcomeMessage | string | Optional | See the request example below. |
quickPrompts | string[] | Optional | Starter questions shown before the first message. |
avatarUrl | string | Optional | Assistant avatar image. https:// URL, up to 2048 characters. An empty string clears it. |
launcherIconUrl | string | Optional | Custom launcher-bubble icon image. https:// URL, up to 2048 characters. An empty string clears it. |
ctaChips | object[] | Optional | Call-to-action chips: { label, url } each, at most 3 kept. Labels up to 40 characters; URLs https:// or mailto:. An empty array clears them. |
proactiveTriggers | object[] | Optional | Proactive teasers: { message, trigger, value?, pagePattern? } each, at most 3 kept. message is 1–200 characters. trigger is delay (value required: whole seconds, 5–600), scroll (value required: whole percent, 10–90), or exit (value must be omitted). pagePattern starts with /, up to 200 characters, with one optional trailing * wildcard. An empty array clears them. |
leadCaptureEnabled | boolean | Optional | Offer the lead form when the documents cannot answer. |
leadCaptureTitle | string | Optional | Heading above the lead form. |
leadCaptureFields | "name" | "email"[] | Optional | Which built-in inputs to show. |
feedbackEnabled | boolean | Optional | Show thumbs up/down on answers. Defaults to on. |
visitorContextEnabled | boolean | Optional | Capture visitor context (referrer, landing page, UTM) and show the consent banner. false is a project-level kill switch — it turns off both for every visitor, overriding any visitor's own consent choice. Defaults to on. |
leadFormFields | object[] | Optional | Additional custom fields. Growth and above. |
theme | object | Optional | Widget theme: primaryColor, position, mode. |
Request
{
"displayName": "Acme Support",
"avatarUrl": "https://cdn.acme.com/bot-avatar.png",
"launcherIconUrl": "https://cdn.acme.com/launcher-icon.svg",
"ctaChips": [
{ "label": "Book a demo", "url": "https://acme.com/demo" },
{ "label": "Email us", "url": "mailto:hello@acme.com" }
],
"proactiveTriggers": [
{
"message": "Questions about pricing? I can help!",
"trigger": "delay",
"value": 30,
"pagePattern": "/pricing*"
}
]
} Response
200 Success
{
"companyId": "co_abc123",
"projectId": "proj_abc123",
"enabled": true,
"displayName": "Acme Support",
"welcomeMessage": "How can I help?",
"avatarUrl": "https://cdn.acme.com/bot-avatar.png",
"launcherIconUrl": "https://cdn.acme.com/launcher-icon.svg",
"ctaChips": [
{ "label": "Book a demo", "url": "https://acme.com/demo" },
{ "label": "Email us", "url": "mailto:hello@acme.com" }
],
"proactiveTriggers": [
{
"message": "Questions about pricing? I can help!",
"trigger": "delay",
"value": 30,
"pagePattern": "/pricing*"
}
],
"theme": {
"primaryColor": "#5046e5",
"position": "bottom-right"
},
"createdAt": "2026-08-11T12:00:00.000Z",
"updatedAt": "2026-08-28T12:00:00.000Z",
"scriptTag": "<script src=\"https://api.dev.oprag.ai/widget.js\" data-project=\"proj_abc123\" data-embed-key=\"YOUR_EMBED_KEY\"></script>"
} Status codes
curl
curl -X PATCH 'https://api.oprag.ai/v1/projects/{projectId}/widget-config' \
-H 'Authorization: Bearer <Cognito JWT>' \
-H 'Content-Type: application/json' \
-d '{"displayName": "Acme Support","avatarUrl": "https://cdn.acme.com/bot-avatar.png","launcherIconUrl": "https://cdn.acme.com/launcher-icon.svg","ctaChips": [{ "label": "Book a demo", "url": "https://acme.com/demo" },{ "label": "Email us", "url": "mailto:hello@acme.com" }],"proactiveTriggers": [{"message": "Questions about pricing? I can help!","trigger": "delay","value": 30,"pagePattern": "/pricing*"}]}' /v1/projects/{projectId}/channels/whatsapp WhatsApp Business channel status for a project, with no secrets in it.
Auth Cognito JWT admin
Before you call it
statusisdisconnected,connected, orerror. Before the first connection,connectedandenabledare both false andstatusisdisconnected.- No secrets are returned — this is a
WhatsAppChannelStatus, in whichtokenConfiguredandwebhookVerifyConfiguredreport only whether they exist.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
projectId | string | Required | Project the request applies to. Must belong to the calling workspace. |
Query parameters
None.Reads the resource named in the path; there is nothing else to select.
Body parameters
None.GET requests carry no body.
Response
200 Success
{
"connected": true,
"enabled": true,
"phoneNumberId": "123456789",
"displayPhoneNumber": "+15551234567",
"wabaId": "987654321",
"tokenConfigured": true,
"webhookVerifyConfigured": true,
"lastInboundAt": "2026-08-11T12:00:00.000Z",
"status": "connected"
} Status codes
curl
curl -X GET 'https://api.oprag.ai/v1/projects/{projectId}/channels/whatsapp' \
-H 'Authorization: Bearer <Cognito JWT>' /v1/projects/{projectId}/channels/whatsapp/connect Attach a WhatsApp Business phone number to the project.
Auth Cognito JWT admin
Before you call it
- Ownership of the number is verified against the Meta Graph API with the supplied token before anything is stored.
accessTokenis encrypted at rest, which requiresCHANNEL_TOKEN_ENCRYPTION_KEYto be configured.- Hub webhook verification always uses the global
WHATSAPP_VERIFY_TOKEN, never a per-project one. - A successful connect returns the project's
WhatsAppChannelStatus, exactly asGET …/channels/whatsappwould.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
projectId | string | Required | Project the request applies to. Must belong to the calling workspace. |
Query parameters
None.Everything it needs is in the request body.
Body parameters
| Name | Type | Required | Description |
|---|---|---|---|
phoneNumberId | string | Required | WhatsApp Business phone number id. |
wabaId | string | Required | WhatsApp Business account id. |
accessToken | string | Required | Long-lived access token. Encrypted at rest. |
displayPhoneNumber | string | Optional | Human-readable number to show in the dashboard. Defaults to the one Meta reports for phoneNumberId. |
Request
{
"phoneNumberId": "123456789",
"wabaId": "987654321",
"accessToken": "EAA...",
"displayPhoneNumber": "+15551234567"
} Response
200 Success
{
"connected": true,
"enabled": true,
"phoneNumberId": "123456789",
"displayPhoneNumber": "+15551234567",
"wabaId": "987654321",
"tokenConfigured": true,
"webhookVerifyConfigured": true,
"status": "connected"
} Status codes
| Status | Meaning |
|---|---|
| 200 | Success. |
| 400 | A required field is missing, or Meta's ownership check for the number failed. |
| 401 | Missing or expired JWT. |
| 403 | The signed-in user's role does not allow this. |
| 404 | No such resource in this workspace. |
| 409 | That phone number is already connected somewhere else. |
| 429 | Rate limited. See rate limits. |
| 503 | Channel token encryption is not configured. |
curl
curl -X POST 'https://api.oprag.ai/v1/projects/{projectId}/channels/whatsapp/connect' \
-H 'Authorization: Bearer <Cognito JWT>' \
-H 'Content-Type: application/json' \
-d '{"phoneNumberId": "123456789","wabaId": "987654321","accessToken": "EAA...","displayPhoneNumber": "+15551234567"}' /v1/projects/{projectId}/channels/whatsapp Pause or resume an existing WhatsApp connection without deleting its credentials.
Auth Cognito JWT admin
Before you call it
- Returns the project's
WhatsAppChannelStatuswith the newenabledvalue.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
projectId | string | Required | Project the request applies to. Must belong to the calling workspace. |
Query parameters
None.Everything it needs is in the request body.
Body parameters
| Name | Type | Required | Description |
|---|---|---|---|
enabled | boolean | Optional | Pause or resume the channel without disconnecting it. |
Request
{ "enabled": false } Response
200 Success
{
"connected": true,
"enabled": false,
"phoneNumberId": "123456789",
"displayPhoneNumber": "+15551234567",
"wabaId": "987654321",
"tokenConfigured": true,
"webhookVerifyConfigured": true,
"status": "connected"
} Status codes
curl
curl -X PATCH 'https://api.oprag.ai/v1/projects/{projectId}/channels/whatsapp' \
-H 'Authorization: Bearer <Cognito JWT>' \
-H 'Content-Type: application/json' \
-d '{ "enabled": false }' /v1/projects/{projectId}/channels/whatsapp Disconnect WhatsApp from the project.
Auth Cognito JWT admin
Before you call it
- Deletes both the connection row and the global
phone_number_idlookup.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
projectId | string | Required | Project the request applies to. Must belong to the calling workspace. |
Query parameters
None.Acts on the resource named in the path; there is nothing to choose.
Body parameters
None.DELETE requests carry no body.
Response
Status codes
curl
curl -X DELETE 'https://api.oprag.ai/v1/projects/{projectId}/channels/whatsapp' \
-H 'Authorization: Bearer <Cognito JWT>' Ready to ship?
Get started free