Merge branch 'develop' into feat/read-only-token

This commit is contained in:
Shivam Mishra
2026-06-02 14:33:28 +05:30
committed by GitHub
54 changed files with 6861 additions and 548 deletions
@@ -92,10 +92,18 @@ class Messages::Facebook::MessageBuilder < Messages::Messenger::MessageBuilder
def fallback_params(attachment)
{
fallback_title: attachment['title'],
external_url: attachment['url']
external_url: attachment['url'] || attachment.dig('payload', 'url')
}
end
# Facebook shared posts point to page URLs, not downloadable media URLs.
# Keep this Facebook-only so Messenger/Instagram share attachments still use the parent media handling.
def normalize_file_type(type)
return :fallback if type.to_sym == :share
super
end
def conversation_params
{
account_id: @inbox.account_id,
@@ -8,7 +8,15 @@ class Api::V1::Accounts::OauthAuthorizationController < Api::V1::Accounts::BaseC
end
def state
Current.account.to_sgid(expires_in: 15.minutes).to_s
# The sgid purpose doubles as a return hint: onboarding tags it so the callback
# can route the user back to inbox setup. The purpose is part of the signed
# payload (tamper-proof), and a non-onboarding request keeps the default
# purpose, leaving callers like Notion byte-identical.
Current.account.to_sgid(expires_in: 15.minutes, for: state_purpose).to_s
end
def state_purpose
params[:return_to] == 'onboarding' ? 'onboarding' : 'default'
end
def base_url
@@ -3,7 +3,7 @@ class Api::V1::Accounts::Tiktok::AuthorizationsController < Api::V1::Accounts::O
def create
redirect_url = Tiktok::AuthClient.authorize_url(
state: generate_tiktok_token(Current.account.id)
state: generate_tiktok_token(Current.account.id, params[:return_to])
)
if redirect_url
@@ -14,4 +14,11 @@ class Microsoft::CallbacksController < OauthCallbackController
def imap_address
'outlook.office365.com'
end
# Exchange Online's SMTP AUTH (XOAUTH2) rejects proxy addresses in the SASL `user=` field;
# it must match the token's UPN. `preferred_username` is the documented v2.0 claim;
# `upn` is the v1.0 fallback.
def imap_login_identity
users_data['preferred_username'] || users_data['upn'] || super
end
end
+25 -2
View File
@@ -16,6 +16,8 @@ class OauthCallbackController < ApplicationController
def handle_response
inbox, already_exists = find_or_create_inbox
return redirect_to app_onboarding_inbox_setup_url(account_id: account.id) if return_to == 'onboarding'
if already_exists
redirect_to app_email_inbox_settings_url(account_id: account.id, inbox_id: inbox.id)
else
@@ -44,7 +46,7 @@ class OauthCallbackController < ApplicationController
def update_channel(channel_email)
channel_email.update!({
imap_login: users_data['email'], imap_address: imap_address,
imap_login: imap_login_identity, imap_address: imap_address,
imap_port: '993', imap_enabled: true,
provider: provider_name,
provider_config: {
@@ -55,6 +57,13 @@ class OauthCallbackController < ApplicationController
})
end
# Identity used as the IMAP/SMTP login (SASL XOAUTH2 `user=` field). Defaults to the
# id_token's email claim; providers override when their server requires a different
# claim (e.g. Microsoft SMTP requires UPN).
def imap_login_identity
users_data['email']
end
def provider_name
raise NotImplementedError
end
@@ -81,10 +90,19 @@ class OauthCallbackController < ApplicationController
decoded_token[0]
end
# The sgid purpose carries the onboarding return hint (see
# OauthAuthorizationController#state). Try the onboarding purpose first — a match
# both resolves the account and records the return target — then fall back to the
# default purpose used by every other caller.
def account_from_signed_id
raise ActionController::BadRequest, 'Missing state variable' if params[:state].blank?
account = GlobalID::Locator.locate_signed(params[:state])
if (account = GlobalID::Locator.locate_signed(params[:state], for: 'onboarding'))
@return_to = 'onboarding'
else
account = GlobalID::Locator.locate_signed(params[:state])
end
raise 'Invalid or expired state' if account.nil?
account
@@ -94,6 +112,11 @@ class OauthCallbackController < ApplicationController
@account ||= account_from_signed_id
end
def return_to
account # resolving the sgid records which purpose matched
@return_to
end
# Fallback name, for when name field is missing from users_data
def fallback_name
users_data['email'].split('@').first.parameterize.titleize
@@ -20,6 +20,8 @@ class Tiktok::CallbacksController < ApplicationController
def process_successful_authorization
inbox, already_exists = find_or_create_inbox
return redirect_to app_onboarding_inbox_setup_url(account_id: account_id) if return_to == 'onboarding'
if already_exists
redirect_to app_tiktok_inbox_settings_url(account_id: account_id, inbox_id: inbox.id)
else
@@ -127,6 +129,10 @@ class Tiktok::CallbacksController < ApplicationController
@account_id ||= verify_tiktok_token(params[:state])
end
def return_to
tiktok_token_return_to(params[:state])
end
def account
@account ||= Account.find(account_id)
end
+16 -9
View File
@@ -2,11 +2,12 @@ module Tiktok::IntegrationHelper
# Generates a signed JWT token for Tiktok integration
#
# @param account_id [Integer] The account ID to encode in the token
# @param return_to [String, nil] Optional onboarding return hint
# @return [String, nil] The encoded JWT token or nil if client secret is missing
def generate_tiktok_token(account_id)
def generate_tiktok_token(account_id, return_to = nil)
return if client_secret.blank?
JWT.encode(token_payload(account_id), client_secret, 'HS256')
JWT.encode(token_payload(account_id, return_to), client_secret, 'HS256')
rescue StandardError => e
Rails.logger.error("Failed to generate TikTok token: #{e.message}")
nil
@@ -19,7 +20,14 @@ module Tiktok::IntegrationHelper
def verify_tiktok_token(token)
return if token.blank? || client_secret.blank?
decode_token(token, client_secret)
decode_token(token, client_secret)&.dig('sub')
end
# Reads the onboarding return hint from a Tiktok JWT token, if present.
def tiktok_token_return_to(token)
return if token.blank? || client_secret.blank?
decode_token(token, client_secret)&.dig('return_to')
end
private
@@ -28,18 +36,17 @@ module Tiktok::IntegrationHelper
@client_secret ||= GlobalConfigService.load('TIKTOK_APP_SECRET', nil)
end
def token_payload(account_id)
{
sub: account_id,
iat: Time.current.to_i
}
def token_payload(account_id, return_to = nil)
payload = { sub: account_id, iat: Time.current.to_i }
payload[:return_to] = return_to if return_to.present?
payload
end
def decode_token(token, secret)
JWT.decode(token, secret, true, {
algorithm: 'HS256',
verify_expiration: true
}).first['sub']
}).first
rescue StandardError => e
Rails.logger.error("Unexpected error verifying Tiktok token: #{e.message}")
nil
@@ -49,6 +49,7 @@ const emit = defineEmits(['edit', 'delete']);
const { t } = useI18n();
const STATUS_COMPLETED = 'completed';
const STATUS_PROCESSING = 'processing';
const { formatMessage } = useMessageFormatter();
@@ -68,9 +69,15 @@ const campaignStatus = computed(() => {
: t('CAMPAIGN.LIVE_CHAT.CARD.STATUS.DISABLED');
}
return props.status === STATUS_COMPLETED
? t('CAMPAIGN.SMS.CARD.STATUS.COMPLETED')
: t('CAMPAIGN.SMS.CARD.STATUS.SCHEDULED');
if (props.status === STATUS_COMPLETED) {
return t('CAMPAIGN.SMS.CARD.STATUS.COMPLETED');
}
if (props.status === STATUS_PROCESSING) {
return t('CAMPAIGN.SMS.CARD.STATUS.PROCESSING');
}
return t('CAMPAIGN.SMS.CARD.STATUS.SCHEDULED');
});
const inboxName = computed(() => props.inbox?.name || '');
@@ -1,34 +1,48 @@
<script setup>
import { ref } from 'vue';
import { computed, ref } from 'vue';
import { useI18n } from 'vue-i18n';
import Button from 'dashboard/components-next/button/Button.vue';
import DropdownMenu from 'dashboard/components-next/dropdown-menu/DropdownMenu.vue';
import { usePolicy } from 'dashboard/composables/usePolicy';
const emit = defineEmits(['add', 'import', 'export']);
const { t } = useI18n();
const { checkPermissions } = usePolicy();
const contactMenuItems = [
const contactMenuItems = computed(() => [
{
label: t('CONTACTS_LAYOUT.HEADER.ACTIONS.CONTACT_CREATION.ADD_CONTACT'),
action: 'add',
value: 'add',
icon: 'i-lucide-plus',
},
{
label: t('CONTACTS_LAYOUT.HEADER.ACTIONS.CONTACT_CREATION.EXPORT_CONTACT'),
action: 'export',
value: 'export',
icon: 'i-lucide-upload',
},
{
label: t('CONTACTS_LAYOUT.HEADER.ACTIONS.CONTACT_CREATION.IMPORT_CONTACT'),
action: 'import',
value: 'import',
icon: 'i-lucide-download',
},
];
...(checkPermissions(['administrator', 'contact_manage'])
? [
{
label: t(
'CONTACTS_LAYOUT.HEADER.ACTIONS.CONTACT_CREATION.EXPORT_CONTACT'
),
action: 'export',
value: 'export',
icon: 'i-lucide-upload',
},
]
: []),
...(checkPermissions(['administrator', 'contact_manage'])
? [
{
label: t(
'CONTACTS_LAYOUT.HEADER.ACTIONS.CONTACT_CREATION.IMPORT_CONTACT'
),
action: 'import',
value: 'import',
icon: 'i-lucide-download',
},
]
: []),
]);
const showActionsDropdown = ref(false);
const handleContactAction = ({ action }) => {
@@ -31,6 +31,7 @@ import FileBubble from './bubbles/File.vue';
import AudioBubble from './bubbles/Audio.vue';
import VideoBubble from './bubbles/Video.vue';
import EmbedBubble from './bubbles/Embed.vue';
import FallbackBubble from './bubbles/Fallback.vue';
import InstagramStoryBubble from './bubbles/InstagramStory.vue';
import EmailBubble from './bubbles/Email/Index.vue';
import UnsupportedBubble from './bubbles/Unsupported.vue';
@@ -328,6 +329,8 @@ const componentToRender = computed(() => {
if (Array.isArray(props.attachments) && props.attachments.length === 1) {
const fileType = props.attachments[0].fileType;
if (fileType === ATTACHMENT_TYPES.FALLBACK) return FallbackBubble;
if (!props.content) {
if (fileType === ATTACHMENT_TYPES.IMAGE) return ImageBubble;
if (fileType === ATTACHMENT_TYPES.FILE) return FileBubble;
@@ -0,0 +1,37 @@
<script setup>
import { computed } from 'vue';
import BaseBubble from './Base.vue';
import FormattedContent from './Text/FormattedContent.vue';
import { useMessageContext } from '../provider.js';
const { attachments, content } = useMessageContext();
const attachment = computed(() => attachments.value?.[0] || {});
const url = computed(
() => attachment.value.dataUrl || attachment.value.data_url
);
const title = computed(
() =>
attachment.value.fallbackTitle ||
attachment.value.fallback_title ||
url.value
);
</script>
<template>
<BaseBubble class="p-3" data-bubble-name="fallback">
<FormattedContent v-if="content" :content="content" class="mb-2" />
<a
v-if="url"
:href="url"
target="_blank"
rel="noopener noreferrer"
class="block max-w-[320px] truncate text-sm text-n-brand underline"
>
{{ title }}
</a>
<span v-else class="text-sm text-n-slate-11">
{{ title }}
</span>
</BaseBubble>
</template>
@@ -88,6 +88,7 @@
},
"CARD": {
"STATUS": {
"PROCESSING": "Processing",
"COMPLETED": "Completed",
"SCHEDULED": "Scheduled"
},
@@ -146,6 +147,7 @@
},
"CARD": {
"STATUS": {
"PROCESSING": "Processing",
"COMPLETED": "Completed",
"SCHEDULED": "Scheduled"
},
+17 -3
View File
@@ -47,7 +47,7 @@ class Campaign < ApplicationRecord
enum campaign_type: { ongoing: 0, one_off: 1 }
# TODO : enabled attribute is unneccessary . lets move that to the campaign status with additional statuses like draft, disabled etc.
enum campaign_status: { active: 0, completed: 1 }
enum campaign_status: { active: 0, completed: 1, processing: 2 }
has_many :conversations, dependent: :nullify, autosave: true
@@ -56,13 +56,27 @@ class Campaign < ApplicationRecord
def trigger!
return unless one_off?
return if completed?
return unless feature_enabled?
return unless mark_processing!
execute_campaign
end
private
def feature_enabled?
inbox.inbox_type != 'Whatsapp' || account.feature_enabled?(:whatsapp_campaign)
end
def mark_processing!
# Multiple scheduler jobs can pick the same active campaign; lock before flipping status to avoid duplicate sends.
with_lock do
next if completed? || processing?
processing!
end
end
def execute_campaign
case inbox.inbox_type
when 'Twilio SMS'
@@ -70,7 +84,7 @@ class Campaign < ApplicationRecord
when 'Sms'
Sms::OneoffSmsCampaignService.new(campaign: self).perform
when 'Whatsapp'
Whatsapp::OneoffCampaignService.new(campaign: self).perform if account.feature_enabled?(:whatsapp_campaign)
Whatsapp::OneoffCampaignService.new(campaign: self).perform
end
end
+2
View File
@@ -51,3 +51,5 @@ class ContactPolicy < ApplicationPolicy
@account_user.administrator?
end
end
ContactPolicy.prepend_mod_with('ContactPolicy')
@@ -5,12 +5,10 @@ class Sms::OneoffSmsCampaignService
raise "Invalid campaign #{campaign.id}" if campaign.inbox.inbox_type != 'Sms' || !campaign.one_off?
raise 'Completed Campaign' if campaign.completed?
# marks campaign completed so that other jobs won't pick it up
campaign.completed!
audience_label_ids = campaign.audience.select { |audience| audience['type'] == 'Label' }.pluck('id')
audience_labels = campaign.account.labels.where(id: audience_label_ids).pluck(:title)
process_audience(audience_labels)
campaign.completed!
end
private
@@ -5,12 +5,10 @@ class Twilio::OneoffSmsCampaignService
raise "Invalid campaign #{campaign.id}" if campaign.inbox.inbox_type != 'Twilio SMS' || !campaign.one_off?
raise 'Completed Campaign' if campaign.completed?
# marks campaign completed so that other jobs won't pick it up
campaign.completed!
audience_label_ids = campaign.audience.select { |audience| audience['type'] == 'Label' }.pluck('id')
audience_labels = campaign.account.labels.where(id: audience_label_ids).pluck(:title)
process_audience(audience_labels)
campaign.completed!
end
private
@@ -3,9 +3,8 @@ class Whatsapp::OneoffCampaignService
def perform
validate_campaign!
# marks campaign completed so that other jobs won't pick it up
campaign.completed!
process_audience(extract_audience_labels)
campaign.completed!
end
private
+1
View File
@@ -29,6 +29,7 @@ Rails.application.routes.draw do
get '/app/accounts/:account_id/settings/inboxes/:inbox_id', to: 'dashboard#index', as: 'app_instagram_inbox_settings'
get '/app/accounts/:account_id/settings/inboxes/:inbox_id', to: 'dashboard#index', as: 'app_tiktok_inbox_settings'
get '/app/accounts/:account_id/settings/inboxes/:inbox_id', to: 'dashboard#index', as: 'app_email_inbox_settings'
get '/app/accounts/:account_id/onboarding/inbox-setup', to: 'dashboard#index', as: 'app_onboarding_inbox_setup'
resource :widget, only: [:show]
namespace :survey do
@@ -0,0 +1,9 @@
module Enterprise::ContactPolicy
def export?
@account_user.custom_role&.permissions&.include?('contact_manage') || super
end
def import?
@account_user.custom_role&.permissions&.include?('contact_manage') || super
end
end
@@ -19,7 +19,15 @@ class Enterprise::Billing::ReconcilePlanFeaturesService
channel_voice
].freeze
BUSINESS_PLAN_FEATURES = %w[sla custom_roles csat_review_notes conversation_required_attributes advanced_assignment custom_tools].freeze
BUSINESS_PLAN_FEATURES = %w[
sla
custom_roles
csat_review_notes
conversation_required_attributes
advanced_assignment
custom_tools
companies
].freeze
ENTERPRISE_PLAN_FEATURES = %w[audit_logs disable_branding saml].freeze
PREMIUM_PLAN_FEATURES = (STARTUP_PLAN_FEATURES + BUSINESS_PLAN_FEATURES + ENTERPRISE_PLAN_FEATURES).freeze
@@ -140,6 +140,45 @@ describe Messages::Facebook::MessageBuilder do
end
end
[
{
source_id: 'm_fallback_test',
attachment: { type: 'fallback', title: 'Shared link', url: 'https://www.example.com/shared-link' },
title: 'Shared link',
url: 'https://www.example.com/shared-link'
},
{
source_id: 'm_share_test',
attachment: { type: 'share', title: 'Shared Facebook post', payload: { url: 'https://www.facebook.com/example/posts/123' } },
title: 'Shared Facebook post',
url: 'https://www.facebook.com/example/posts/123'
}
].each do |message_data|
it "stores #{message_data[:attachment][:type]} attachments as fallback links" do
allow(Koala::Facebook::API).to receive(:new).and_return(fb_object)
allow(fb_object).to receive(:get_object).and_return(
{ first_name: 'Jane', last_name: 'Dae', profile_pic: 'https://chatwoot-assets.local/sample.png' }.with_indifferent_access
)
expect(Down).not_to receive(:download)
message_object = {
messaging: {
sender: { id: '3383290475046708' },
recipient: { id: facebook_channel.page_id },
message: { mid: message_data[:source_id], attachments: [message_data[:attachment]] }
}
}.to_json
message = Integrations::Facebook::MessageParser.new(message_object)
described_class.new(message, facebook_channel.inbox).perform
attachment = facebook_channel.inbox.messages.find_by(source_id: message_data[:source_id]).attachments.first
expect(attachment.file_type).to eq('fallback')
expect(attachment.fallback_title).to eq(message_data[:title])
expect(attachment.external_url).to eq(message_data[:url])
end
end
context 'when lock to single conversation' do
subject(:mocked_message_builder) do
described_class.new(mocked_incoming_fb_text_message, facebook_channel.inbox).perform
@@ -34,6 +34,25 @@ RSpec.describe 'Microsoft::CallbacksController', type: :request do
expect(inbox.channel.imap_address).to eq 'outlook.office365.com'
end
it 'sets imap_login from preferred_username when the id_token carries a UPN that differs from email' do
upn = 'testaccount@primary-domain.example'
mailbox = 'TestAccount@mailbox-domain.example'
response_body = {
id_token: JWT.encode({ email: mailbox, preferred_username: upn, name: 'test' }, nil, 'none'),
access_token: SecureRandom.hex(10), token_type: 'Bearer', refresh_token: SecureRandom.hex(10)
}
stub_request(:post, 'https://login.microsoftonline.com/common/oauth2/v2.0/token')
.with(body: { 'code' => code, 'grant_type' => 'authorization_code',
'redirect_uri' => "#{ENV.fetch('FRONTEND_URL', 'http://localhost:3000')}/microsoft/callback" })
.to_return(status: 200, body: response_body.to_json, headers: { 'Content-Type' => 'application/json' })
get microsoft_callback_url, params: { code: code, state: state }
channel = account.inboxes.last.channel
expect(channel.imap_login).to eq upn
expect(channel.email).to eq mailbox
end
it 'creates updates inbox channel config if inbox exists and authentication is successful' do
inbox = create(:channel_email, account: account, email: email)&.inbox
expect(inbox.channel.provider_config).to eq({})
@@ -0,0 +1,26 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe 'Enterprise::ContactPolicy', type: :policy do
subject(:contact_policy) { ContactPolicy }
let(:account) { create(:account) }
let(:contact) { create(:contact, account: account) }
let(:custom_role) { create(:custom_role, account: account, permissions: ['contact_manage']) }
let(:agent) { create(:user) }
let(:account_user) { create(:account_user, user: agent, account: account, role: :agent, custom_role: custom_role) }
let(:agent_context) { { user: agent, account: account, account_user: account_user } }
permissions :export? do
context 'when agent has contact_manage permission' do
it { expect(contact_policy).to permit(agent_context, contact) }
end
end
permissions :import? do
context 'when agent has contact_manage permission' do
it { expect(contact_policy).to permit(agent_context, contact) }
end
end
end
@@ -40,5 +40,13 @@ RSpec.describe TriggerScheduledItemsJob do
expect(Campaigns::TriggerOneoffCampaignJob).to receive(:perform_later).with(campaign).once
described_class.perform_now
end
it 'does not trigger campaigns that are already processing' do
create(:campaign, inbox: twilio_inbox, account: account, campaign_status: :processing)
expect(Campaigns::TriggerOneoffCampaignJob).not_to receive(:perform_later)
described_class.perform_now
end
end
end
+48
View File
@@ -83,6 +83,38 @@ RSpec.describe Campaign do
campaign.save!
campaign.trigger!
end
it 'marks the campaign as processing before triggering the service' do
campaign.save!
sms_service = double
expect(Twilio::OneoffSmsCampaignService).to receive(:new).with(campaign: campaign).and_return(sms_service)
expect(sms_service).to receive(:perform) do
expect(campaign.reload.processing?).to be true
end
campaign.trigger!
end
it 'does not trigger a processing campaign again' do
campaign.save!
campaign.processing!
expect(Twilio::OneoffSmsCampaignService).not_to receive(:new)
campaign.trigger!
end
it 'keeps the campaign processing when triggering fails' do
campaign.save!
sms_service = double
expect(Twilio::OneoffSmsCampaignService).to receive(:new).with(campaign: campaign).and_return(sms_service)
expect(sms_service).to receive(:perform).and_raise(StandardError, 'provider error')
expect { campaign.trigger! }.to raise_error(StandardError, 'provider error')
expect(campaign.reload.processing?).to be true
end
end
context 'when SMS campaign' do
@@ -107,6 +139,22 @@ RSpec.describe Campaign do
end
end
context 'when WhatsApp campaign feature is disabled' do
let(:account) { create(:account) }
let(:whatsapp_channel) do
create(:channel_whatsapp, account: account, provider: 'whatsapp_cloud', validate_provider_config: false, sync_templates: false)
end
let(:campaign) { create(:campaign, account: account, inbox: whatsapp_channel.inbox) }
it 'does not mark the campaign as processing' do
expect(Whatsapp::OneoffCampaignService).not_to receive(:new)
campaign.trigger!
expect(campaign.reload.active?).to be true
end
end
context 'when Website campaign' do
let(:campaign) { build(:campaign) }
@@ -45,6 +45,19 @@ describe Sms::OneoffSmsCampaignService do
expect(campaign.reload.completed?).to be true
end
it 'marks the campaign completed after processing the audience' do
contact = create(:contact, :with_phone_number, account: account)
contact.update_labels([label1.title])
expect(sms_channel).to receive(:send_text_message) do
expect(campaign.reload.completed?).to be false
end
sms_campaign_service.perform
expect(campaign.reload.completed?).to be true
end
it 'uses liquid template service to process campaign message' do
contact = create(:contact, :with_phone_number, account: account)
contact.update_labels([label1.title])
@@ -61,6 +61,24 @@ describe Twilio::OneoffSmsCampaignService do
expect(campaign.reload.completed?).to be true
end
it 'marks the campaign completed after processing the audience' do
contact = create(:contact, :with_phone_number, account: account)
contact.update_labels([label1.title])
expect(twilio_messages).to receive(:create).with(
body: campaign.message,
messaging_service_sid: twilio_sms.messaging_service_sid,
to: contact.phone_number,
status_callback: 'http://localhost:3000/twilio/delivery_status'
) do
expect(campaign.reload.completed?).to be false
end
sms_campaign_service.perform
expect(campaign.reload.completed?).to be true
end
it 'uses liquid template service to process campaign message' do
contact = create(:contact, :with_phone_number, account: account)
contact.update_labels([label1.title])
@@ -82,6 +82,19 @@ describe Whatsapp::OneoffCampaignService do
expect(campaign.reload.completed?).to be true
end
it 'marks the campaign completed after processing the audience' do
contact = create(:contact, :with_phone_number, account: account)
contact.update_labels([label1.title])
expect(whatsapp_channel).to receive(:send_template) do
expect(campaign.reload.completed?).to be false
end
described_class.new(campaign: campaign).perform
expect(campaign.reload.completed?).to be true
end
it 'processes contacts with matching labels' do
contact_with_label1, contact_with_label2, contact_with_both_labels =
create_list(:contact, 3, :with_phone_number, account: account)
+28
View File
@@ -145,6 +145,34 @@ inbox_create_payload:
$ref: ./request/inbox/create_payload.yml
inbox_update_payload:
$ref: ./request/inbox/update_payload.yml
inbox_create_web_widget_channel_payload:
$ref: ./request/inbox/channels/create_web_widget_channel_payload.yml
inbox_create_api_channel_payload:
$ref: ./request/inbox/channels/create_api_channel_payload.yml
inbox_create_email_channel_payload:
$ref: ./request/inbox/channels/create_email_channel_payload.yml
inbox_create_line_channel_payload:
$ref: ./request/inbox/channels/create_line_channel_payload.yml
inbox_create_telegram_channel_payload:
$ref: ./request/inbox/channels/create_telegram_channel_payload.yml
inbox_create_whatsapp_channel_payload:
$ref: ./request/inbox/channels/create_whatsapp_channel_payload.yml
inbox_create_sms_channel_payload:
$ref: ./request/inbox/channels/create_sms_channel_payload.yml
inbox_update_web_widget_channel_payload:
$ref: ./request/inbox/channels/update_web_widget_channel_payload.yml
inbox_update_api_channel_payload:
$ref: ./request/inbox/channels/update_api_channel_payload.yml
inbox_update_email_channel_payload:
$ref: ./request/inbox/channels/update_email_channel_payload.yml
inbox_update_line_channel_payload:
$ref: ./request/inbox/channels/update_line_channel_payload.yml
inbox_update_telegram_channel_payload:
$ref: ./request/inbox/channels/update_telegram_channel_payload.yml
inbox_update_whatsapp_channel_payload:
$ref: ./request/inbox/channels/update_whatsapp_channel_payload.yml
inbox_update_sms_channel_payload:
$ref: ./request/inbox/channels/update_sms_channel_payload.yml
# Team
team_create_update_payload:
@@ -0,0 +1,22 @@
type: object
title: API channel
required:
- type
properties:
type:
type: string
enum: ['api']
example: api
webhook_url:
type: string
description: Webhook URL for API channel inbox callbacks
example: 'https://example.com/webhook'
hmac_mandatory:
type: boolean
description: Require HMAC verification for incoming API channel messages
example: false
additional_attributes:
type: object
description: Additional attributes stored on contacts created through the API channel
example:
source: mobile_app
@@ -0,0 +1,90 @@
type: object
title: Email channel
required:
- type
- email
properties:
type:
type: string
enum: ['email']
example: email
email:
type: string
description: Email address for the inbox
example: support@example.com
imap_enabled:
type: boolean
description: Enable IMAP for inbound emails
example: true
imap_login:
type: string
description: IMAP login username
example: support@example.com
imap_password:
type: string
description: IMAP login password
example: your-imap-password
imap_address:
type: string
description: IMAP server address
example: imap.example.com
imap_port:
type: integer
description: IMAP server port
example: 993
imap_enable_ssl:
type: boolean
description: Enable SSL for IMAP
example: true
imap_authentication:
type: string
description: IMAP authentication method
example: plain
smtp_enabled:
type: boolean
description: Enable SMTP for outbound emails
example: true
smtp_login:
type: string
description: SMTP login username
example: support@example.com
smtp_password:
type: string
description: SMTP login password
example: your-smtp-password
smtp_address:
type: string
description: SMTP server address
example: smtp.example.com
smtp_port:
type: integer
description: SMTP server port
example: 587
smtp_domain:
type: string
description: SMTP HELO domain
example: example.com
smtp_enable_starttls_auto:
type: boolean
description: Automatically enable STARTTLS for SMTP
example: true
smtp_enable_ssl_tls:
type: boolean
description: Enable SSL/TLS for SMTP
example: false
smtp_openssl_verify_mode:
type: string
description: OpenSSL certificate verification mode for SMTP
example: none
smtp_authentication:
type: string
description: SMTP authentication method
example: login
provider:
type: string
description: Email provider
example: google
verified_for_sending:
type: boolean
description: Whether the inbox is verified for sending emails
example: false
@@ -0,0 +1,24 @@
type: object
title: LINE channel
required:
- type
- line_channel_id
- line_channel_secret
- line_channel_token
properties:
type:
type: string
enum: ['line']
example: line
line_channel_id:
type: string
description: LINE channel ID
example: '1234567890'
line_channel_secret:
type: string
description: LINE channel secret
example: line-channel-secret
line_channel_token:
type: string
description: LINE channel access token
example: line-channel-token
@@ -0,0 +1,20 @@
type: object
title: SMS channel
required:
- type
- phone_number
properties:
type:
type: string
enum: ['sms']
example: sms
phone_number:
type: string
description: SMS phone number
example: '+15551234567'
provider_config:
type: object
description: Provider-specific SMS configuration
example:
account_id: your-account-id
application_id: your-application-id
@@ -0,0 +1,14 @@
type: object
title: Telegram channel
required:
- type
- bot_token
properties:
type:
type: string
enum: ['telegram']
example: telegram
bot_token:
type: string
description: Telegram bot token
example: 123456789:telegram-bot-token
@@ -0,0 +1,66 @@
type: object
title: Website channel
required:
- type
- website_url
properties:
type:
type: string
enum: ['web_widget']
example: web_widget
website_url:
type: string
description: URL at which the widget will be loaded
example: 'https://example.com'
welcome_title:
type: string
description: Welcome title to be displayed on the widget
example: 'Welcome to our support'
welcome_tagline:
type: string
description: Welcome tagline to be displayed on the widget
example: 'We are here to help you'
widget_color:
type: string
description: A Hex-color string used to customize the widget
example: '#FF5733'
reply_time:
type: string
description: Expected reply time shown on the widget
enum: ['in_a_few_minutes', 'in_a_few_hours', 'in_a_day']
example: in_a_few_minutes
pre_chat_form_enabled:
type: boolean
description: Enable the pre-chat form before starting a conversation
example: false
pre_chat_form_options:
type: object
description: Pre-chat form configuration
example:
pre_chat_message: Share your queries or comments here.
pre_chat_fields:
- field_type: standard
label: Email Id
name: emailAddress
type: email
required: true
enabled: true
continuity_via_email:
type: boolean
description: Continue conversations over email when the contact leaves the website
example: true
hmac_mandatory:
type: boolean
description: Require HMAC verification for contacts using the widget
example: false
allowed_domains:
type: string
description: Comma-separated list of domains where the widget is allowed to load
example: example.com
selected_feature_flags:
type: array
description: Enabled widget feature flags
items:
type: string
enum: ['attachments', 'emoji_picker', 'end_conversation', 'use_inbox_avatar_for_bot', 'allow_mobile_webview']
example: ['attachments', 'emoji_picker', 'end_conversation']
@@ -0,0 +1,80 @@
oneOf:
- type: object
title: WhatsApp Cloud channel
required:
- type
- phone_number
- provider
- provider_config
properties:
type:
type: string
enum: ['whatsapp']
example: whatsapp
phone_number:
type: string
description: WhatsApp phone number
example: '+15551234567'
provider:
type: string
description: WhatsApp provider
enum: ['whatsapp_cloud']
example: whatsapp_cloud
provider_config:
type: object
description: WhatsApp Cloud provider configuration
required:
- api_key
- phone_number_id
- business_account_id
properties:
api_key:
type: string
description: WhatsApp Cloud API key
example: your-api-key
phone_number_id:
type: string
description: Phone number ID for WhatsApp Cloud
example: your-phone-number-id
business_account_id:
type: string
description: Business account ID for WhatsApp Cloud
example: your-business-account-id
example:
api_key: your-api-key
phone_number_id: your-phone-number-id
business_account_id: your-business-account-id
- type: object
title: Legacy 360dialog WhatsApp channel
deprecated: true
required:
- type
- phone_number
- provider_config
properties:
type:
type: string
enum: ['whatsapp']
example: whatsapp
phone_number:
type: string
description: WhatsApp phone number
example: '+15551234567'
provider:
type: string
description: Legacy 360dialog provider. Omit this field or use `default` only for existing deprecated 360dialog setups.
enum: ['default']
deprecated: true
example: default
provider_config:
type: object
description: Legacy 360dialog provider configuration
required:
- api_key
properties:
api_key:
type: string
description: 360dialog API key
example: your-api-key
example:
api_key: your-api-key
@@ -0,0 +1,16 @@
type: object
title: API channel settings
properties:
webhook_url:
type: string
description: Webhook URL for API channel inbox callbacks
example: 'https://example.com/webhook'
hmac_mandatory:
type: boolean
description: Require HMAC verification for incoming API channel messages
example: false
additional_attributes:
type: object
description: Additional attributes stored on contacts created through the API channel
example:
source: mobile_app
@@ -0,0 +1,83 @@
type: object
title: Email channel settings
properties:
email:
type: string
description: Email address for the inbox
example: support@example.com
imap_enabled:
type: boolean
description: Enable IMAP for inbound emails
example: true
imap_login:
type: string
description: IMAP login username
example: support@example.com
imap_password:
type: string
description: IMAP login password
example: your-imap-password
imap_address:
type: string
description: IMAP server address
example: imap.example.com
imap_port:
type: integer
description: IMAP server port
example: 993
imap_enable_ssl:
type: boolean
description: Enable SSL for IMAP
example: true
imap_authentication:
type: string
description: IMAP authentication method
example: plain
smtp_enabled:
type: boolean
description: Enable SMTP for outbound emails
example: true
smtp_login:
type: string
description: SMTP login username
example: support@example.com
smtp_password:
type: string
description: SMTP login password
example: your-smtp-password
smtp_address:
type: string
description: SMTP server address
example: smtp.example.com
smtp_port:
type: integer
description: SMTP server port
example: 587
smtp_domain:
type: string
description: SMTP HELO domain
example: example.com
smtp_enable_starttls_auto:
type: boolean
description: Automatically enable STARTTLS for SMTP
example: true
smtp_enable_ssl_tls:
type: boolean
description: Enable SSL/TLS for SMTP
example: false
smtp_openssl_verify_mode:
type: string
description: OpenSSL certificate verification mode for SMTP
example: none
smtp_authentication:
type: string
description: SMTP authentication method
example: login
provider:
type: string
description: Email provider
example: google
verified_for_sending:
type: boolean
description: Whether the inbox is verified for sending emails
example: false
@@ -0,0 +1,15 @@
type: object
title: LINE channel settings
properties:
line_channel_id:
type: string
description: LINE channel ID
example: '1234567890'
line_channel_secret:
type: string
description: LINE channel secret
example: line-channel-secret
line_channel_token:
type: string
description: LINE channel access token
example: line-channel-token
@@ -0,0 +1,15 @@
type: object
title: SMS channel settings
properties:
phone_number:
type: string
description: SMS phone number
example: '+15551234567'
provider_config:
type: object
description: Provider-specific SMS configuration
example:
api_key: your-api-key
api_secret: your-api-secret
application_id: your-application-id
account_id: your-account-id
@@ -0,0 +1,7 @@
type: object
title: Telegram channel settings
properties:
bot_token:
type: string
description: Telegram bot token
example: 123456789:telegram-bot-token
@@ -0,0 +1,59 @@
type: object
title: Website channel settings
properties:
website_url:
type: string
description: URL at which the widget will be loaded
example: 'https://example.com'
welcome_title:
type: string
description: Welcome title to be displayed on the widget
example: 'Welcome to our support'
welcome_tagline:
type: string
description: Welcome tagline to be displayed on the widget
example: 'We are here to help you'
widget_color:
type: string
description: A Hex-color string used to customize the widget
example: '#FF5733'
reply_time:
type: string
description: Expected reply time shown on the widget
enum: ['in_a_few_minutes', 'in_a_few_hours', 'in_a_day']
example: in_a_few_minutes
pre_chat_form_enabled:
type: boolean
description: Enable the pre-chat form before starting a conversation
example: false
pre_chat_form_options:
type: object
description: Pre-chat form configuration
example:
pre_chat_message: Share your queries or comments here.
pre_chat_fields:
- field_type: standard
label: Email Id
name: emailAddress
type: email
required: true
enabled: true
continuity_via_email:
type: boolean
description: Continue conversations over email when the contact leaves the website
example: true
hmac_mandatory:
type: boolean
description: Require HMAC verification for contacts using the widget
example: false
allowed_domains:
type: string
description: Comma-separated list of domains where the widget is allowed to load
example: example.com
selected_feature_flags:
type: array
description: Enabled widget feature flags
items:
type: string
enum: ['attachments', 'emoji_picker', 'end_conversation', 'use_inbox_avatar_for_bot', 'allow_mobile_webview']
example: ['attachments', 'emoji_picker', 'end_conversation']
@@ -0,0 +1,32 @@
type: object
title: WhatsApp channel settings
properties:
phone_number:
type: string
description: WhatsApp phone number
example: '+15551234567'
provider:
type: string
description: WhatsApp provider. `default` is supported only for existing deprecated 360dialog setups.
enum: ['whatsapp_cloud', 'default']
example: whatsapp_cloud
provider_config:
type: object
description: WhatsApp provider configuration. Cloud channels use `api_key`, `phone_number_id`, and `business_account_id`; legacy 360dialog channels use `api_key`.
properties:
api_key:
type: string
description: Provider API key
example: your-api-key
phone_number_id:
type: string
description: Phone number ID for WhatsApp Cloud
example: your-phone-number-id
business_account_id:
type: string
description: Business account ID for WhatsApp Cloud
example: your-business-account-id
example:
api_key: your-api-key
phone_number_id: your-phone-number-id
business_account_id: your-business-account-id
@@ -2,87 +2,129 @@ type: object
properties:
name:
type: string
description: The name of the inbox
description: The name of the inbox.
example: 'Support'
avatar:
type: string
format: binary
description: Image file for avatar
description: Image file for avatar.
greeting_enabled:
type: boolean
description: Enable greeting message
description: Enable greeting message.
example: true
greeting_message:
type: string
description: Greeting message to be displayed on the widget
description: Greeting message to send when greeting messages are enabled.
example: Hello, how can I help you?
enable_email_collect:
type: boolean
description: Enable email collection
description: |
Enable email collection.
Available for: `Website`
example: true
csat_survey_enabled:
type: boolean
description: Enable CSAT survey
description: Enable CSAT survey.
example: true
csat_config:
type: object
description: CSAT survey configuration.
properties:
display_type:
type: string
description: Display style for the CSAT survey.
enum: ['emoji', 'star']
example: emoji
message:
type: string
description: Message shown with the CSAT survey.
example: Please rate your conversation
button_text:
type: string
description: Text shown on the CSAT survey button.
example: Please rate us
language:
type: string
description: Language code for the CSAT survey.
example: en
survey_rules:
type: object
description: Rules that decide when to show the CSAT survey.
properties:
operator:
type: string
example: contains
values:
type: array
items:
type: string
example: ['billing']
enable_auto_assignment:
type: boolean
description: Enable Auto Assignment
description: Enable Auto Assignment.
example: true
working_hours_enabled:
type: boolean
description: Enable working hours
description: Enable working hours.
example: true
out_of_office_message:
type: string
description: Out of office message to be displayed on the widget
description: Out of office message to send outside working hours.
example: We are currently out of office. Please leave a message and we will get back to you.
timezone:
type: string
description: Timezone of the inbox
description: Timezone of the inbox.
example: 'America/New_York'
allow_messages_after_resolved:
type: boolean
description: Allow messages after conversation is resolved
description: |
Allow messages after conversation is resolved.
Available for: `Website`
example: true
lock_to_single_conversation:
type: boolean
description: Lock to single conversation
description: |
Lock contact messages to a single active conversation.
Available for: `API` `LINE` `Telegram` `WhatsApp` `SMS`
example: true
portal_id:
type: integer
description: Id of the help center portal to attach to the inbox
description: Id of the help center portal to attach to the inbox.
example: 1
sender_name_type:
type: string
description: Sender name type for the inbox
description: |
Sender name type for outbound email replies.
Available for: `Website` `Email`
enum: ['friendly', 'professional']
example: 'friendly'
business_name:
type: string
description: Business name for the inbox
description: |
Business name for outbound email replies.
Available for: `Website` `Email`
example: 'My Business'
channel:
type: object
properties:
type:
type: string
description: Type of the channel
enum:
['web_widget', 'api', 'email', 'line', 'telegram', 'whatsapp', 'sms']
example: web_widget
website_url:
type: string
description: URL at which the widget will be loaded
example: 'https://example.com'
welcome_title:
type: string
description: Welcome title to be displayed on the widget
example: 'Welcome to our support'
welcome_tagline:
type: string
description: Welcome tagline to be displayed on the widget
example: 'We are here to help you'
widget_color:
type: string
description: A Hex-color string used to customize the widget
example: '#FF5733'
oneOf:
- $ref: '#/components/schemas/inbox_create_web_widget_channel_payload'
- $ref: '#/components/schemas/inbox_create_api_channel_payload'
- $ref: '#/components/schemas/inbox_create_email_channel_payload'
- $ref: '#/components/schemas/inbox_create_line_channel_payload'
- $ref: '#/components/schemas/inbox_create_telegram_channel_payload'
- $ref: '#/components/schemas/inbox_create_whatsapp_channel_payload'
- $ref: '#/components/schemas/inbox_create_sms_channel_payload'
discriminator:
propertyName: type
mapping:
web_widget: '#/components/schemas/inbox_create_web_widget_channel_payload'
api: '#/components/schemas/inbox_create_api_channel_payload'
email: '#/components/schemas/inbox_create_email_channel_payload'
line: '#/components/schemas/inbox_create_line_channel_payload'
telegram: '#/components/schemas/inbox_create_telegram_channel_payload'
whatsapp: '#/components/schemas/inbox_create_whatsapp_channel_payload'
sms: '#/components/schemas/inbox_create_sms_channel_payload'
@@ -2,81 +2,119 @@ type: object
properties:
name:
type: string
description: The name of the inbox
description: The name of the inbox.
example: 'Support'
avatar:
type: string
format: binary
description: Image file for avatar
description: Image file for avatar.
greeting_enabled:
type: boolean
description: Enable greeting message
description: Enable greeting message.
example: true
greeting_message:
type: string
description: Greeting message to be displayed on the widget
description: Greeting message to send when greeting messages are enabled.
example: Hello, how can I help you?
enable_email_collect:
type: boolean
description: Enable email collection
description: |
Enable email collection.
Available for: `Website`
example: true
csat_survey_enabled:
type: boolean
description: Enable CSAT survey
description: Enable CSAT survey.
example: true
csat_config:
type: object
description: CSAT survey configuration.
properties:
display_type:
type: string
description: Display style for the CSAT survey.
enum: ['emoji', 'star']
example: emoji
message:
type: string
description: Message shown with the CSAT survey.
example: Please rate your conversation
button_text:
type: string
description: Text shown on the CSAT survey button.
example: Please rate us
language:
type: string
description: Language code for the CSAT survey.
example: en
survey_rules:
type: object
description: Rules that decide when to show the CSAT survey.
properties:
operator:
type: string
example: contains
values:
type: array
items:
type: string
example: ['billing']
enable_auto_assignment:
type: boolean
description: Enable Auto Assignment
description: Enable Auto Assignment.
example: true
working_hours_enabled:
type: boolean
description: Enable working hours
description: Enable working hours.
example: true
out_of_office_message:
type: string
description: Out of office message to be displayed on the widget
description: Out of office message to send outside working hours.
example: We are currently out of office. Please leave a message and we will get back to you.
timezone:
type: string
description: Timezone of the inbox
description: Timezone of the inbox.
example: 'America/New_York'
allow_messages_after_resolved:
type: boolean
description: Allow messages after conversation is resolved
description: |
Allow messages after conversation is resolved.
Available for: `Website`
example: true
lock_to_single_conversation:
type: boolean
description: Lock to single conversation
description: |
Lock contact messages to a single active conversation.
Available for: `API` `LINE` `Telegram` `WhatsApp` `SMS`
example: true
portal_id:
type: integer
description: Id of the help center portal to attach to the inbox
description: Id of the help center portal to attach to the inbox.
example: 1
sender_name_type:
type: string
description: Sender name type for the inbox
description: |
Sender name type for outbound email replies.
Available for: `Website` `Email`
enum: ['friendly', 'professional']
example: 'friendly'
business_name:
type: string
description: Business name for the inbox
description: |
Business name for outbound email replies.
Available for: `Website` `Email`
example: 'My Business'
channel:
type: object
properties:
website_url:
type: string
description: URL at which the widget will be loaded
example: 'https://example.com'
welcome_title:
type: string
description: Welcome title to be displayed on the widget
example: 'Welcome to our support'
welcome_tagline:
type: string
description: Welcome tagline to be displayed on the widget
example: 'We are here to help you'
widget_color:
type: string
description: A Hex-color string used to customize the widget
example: '#FF5733'
anyOf:
- $ref: '#/components/schemas/inbox_update_web_widget_channel_payload'
- $ref: '#/components/schemas/inbox_update_api_channel_payload'
- $ref: '#/components/schemas/inbox_update_email_channel_payload'
- $ref: '#/components/schemas/inbox_update_line_channel_payload'
- $ref: '#/components/schemas/inbox_update_telegram_channel_payload'
- $ref: '#/components/schemas/inbox_update_whatsapp_channel_payload'
- $ref: '#/components/schemas/inbox_update_sms_channel_payload'
@@ -4,6 +4,23 @@ operationId: create-a-new-message-in-a-conversation
summary: Create New Message
description: |
Create a new message in the conversation.
Use `application/json` for text messages and `multipart/form-data` when the
message includes file attachments.
### Multipart attachment request
Send files with the `attachments[]` form field. `curl -F` sets the
`multipart/form-data` content type and boundary automatically.
```bash
curl -X POST "https://app.chatwoot.com/api/v1/accounts/{account_id}/conversations/{conversation_id}/messages" \
-H "api_access_token: <your-api-token>" \
-F "content=Here is the screenshot" \
-F "message_type=outgoing" \
-F "private=false" \
-F "attachments[]=@/path/to/screenshot.png"
```
## WhatsApp Template Messages
@@ -62,6 +79,58 @@ requestBody:
application/json:
schema:
$ref: '#/components/schemas/conversation_message_create_payload'
multipart/form-data:
schema:
type: object
description: Form data payload for creating a message with file attachments.
example:
content: Here is the screenshot
message_type: outgoing
private: false
'attachments[]':
- screenshot.png
properties:
content:
type: string
description: The content of the message
example: Here is the screenshot
message_type:
type: string
enum: ['outgoing', 'incoming']
description: The type of the message
example: outgoing
private:
type: boolean
description: Flag to identify if it is a private note
example: false
content_type:
type: string
enum: ['text', 'input_email', 'cards', 'input_select', 'form', 'article']
description: Content type of the message
example: text
content_attributes:
type: object
description: Attributes based on the content type
example: {}
'attachments[]':
type: array
description: Files to attach to the message
items:
type: string
format: binary
encoding:
'attachments[]':
style: form
explode: true
examples:
attachment_message:
summary: Message with an attachment
value:
content: Here is the screenshot
message_type: outgoing
private: false
'attachments[]':
- screenshot.png
responses:
'200':
description: Success
@@ -1,35 +0,0 @@
post:
tags:
- Inboxes
operationId: inboxCreation
summary: Create an inbox
description: You can create more than one website inbox in each account
security:
- userApiKey: []
parameters:
- $ref: '#/components/parameters/account_id'
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/inbox_create_payload'
responses:
'200':
description: Success
content:
application/json:
schema:
$ref: '#/components/schemas/inbox'
'404':
description: Inbox not found
content:
application/json:
schema:
$ref: '#/components/schemas/bad_request_error'
'403':
description: Access denied
content:
application/json:
schema:
$ref: '#/components/schemas/bad_request_error'
+115
View File
@@ -49,6 +49,121 @@ post:
application/json:
schema:
$ref: '#/components/schemas/inbox_create_payload'
examples:
web_widget:
summary: Website inbox
value:
name: Support
greeting_enabled: true
greeting_message: Hello, how can I help you?
enable_email_collect: true
enable_auto_assignment: true
working_hours_enabled: true
timezone: America/New_York
allow_messages_after_resolved: true
channel:
type: web_widget
website_url: https://example.com
welcome_title: Welcome to our support
welcome_tagline: We are here to help you
widget_color: '#FF5733'
reply_time: in_a_few_minutes
pre_chat_form_enabled: false
continuity_via_email: true
hmac_mandatory: false
selected_feature_flags:
- attachments
- emoji_picker
- end_conversation
api:
summary: API channel
value:
name: API Inbox
greeting_enabled: true
greeting_message: Hello, how can I help you?
enable_auto_assignment: true
working_hours_enabled: true
timezone: America/New_York
channel:
type: api
webhook_url: https://example.com/webhook
hmac_mandatory: false
additional_attributes:
source: mobile_app
email:
summary: Email channel
value:
name: Email Inbox
greeting_enabled: true
greeting_message: Hello, how can I help you?
enable_auto_assignment: true
working_hours_enabled: true
timezone: America/New_York
channel:
type: email
email: support@example.com
imap_enabled: false
smtp_enabled: false
line:
summary: LINE channel
value:
name: LINE Inbox
greeting_enabled: true
greeting_message: Hello, how can I help you?
enable_auto_assignment: true
working_hours_enabled: true
timezone: America/New_York
channel:
type: line
line_channel_id: '1234567890'
line_channel_secret: line-channel-secret
line_channel_token: line-channel-token
telegram:
summary: Telegram channel
value:
name: Telegram Inbox
greeting_enabled: true
greeting_message: Hello, how can I help you?
enable_auto_assignment: true
working_hours_enabled: true
timezone: America/New_York
channel:
type: telegram
bot_token: 123456789:telegram-bot-token
whatsapp:
summary: WhatsApp channel
value:
name: WhatsApp Inbox
greeting_enabled: true
greeting_message: Hello, how can I help you?
enable_auto_assignment: true
working_hours_enabled: true
timezone: America/New_York
channel:
type: whatsapp
phone_number: '+15551234567'
provider: whatsapp_cloud
provider_config:
api_key: your-api-key
phone_number_id: your-phone-number-id
business_account_id: your-business-account-id
sms:
summary: SMS channel
value:
name: SMS Inbox
greeting_enabled: true
greeting_message: Hello, how can I help you?
enable_auto_assignment: true
working_hours_enabled: true
timezone: America/New_York
channel:
type: sms
phone_number: '+15551234567'
provider_config:
api_key: your-api-key
api_secret: your-api-secret
application_id: your-application-id
account_id: your-account-id
responses:
'200':
description: Success
@@ -55,6 +55,114 @@ patch:
application/json:
schema:
$ref: '#/components/schemas/inbox_update_payload'
examples:
web_widget:
summary: Website inbox settings
value:
name: Support
greeting_enabled: true
greeting_message: Hello, how can I help you?
enable_email_collect: true
enable_auto_assignment: true
working_hours_enabled: true
timezone: America/New_York
allow_messages_after_resolved: true
channel:
website_url: https://example.com
welcome_title: Welcome to our support
welcome_tagline: We are here to help you
widget_color: '#FF5733'
reply_time: in_a_few_minutes
pre_chat_form_enabled: false
continuity_via_email: true
hmac_mandatory: false
selected_feature_flags:
- attachments
- emoji_picker
- end_conversation
api:
summary: API channel settings
value:
name: API Inbox
greeting_enabled: true
greeting_message: Hello, how can I help you?
enable_auto_assignment: true
working_hours_enabled: true
timezone: America/New_York
channel:
webhook_url: https://example.com/webhook
hmac_mandatory: false
additional_attributes:
source: mobile_app
email:
summary: Email channel settings
value:
name: Email Inbox
greeting_enabled: true
greeting_message: Hello, how can I help you?
enable_auto_assignment: true
working_hours_enabled: true
timezone: America/New_York
channel:
email: support@example.com
imap_enabled: false
smtp_enabled: false
line:
summary: LINE channel settings
value:
name: LINE Inbox
greeting_enabled: true
greeting_message: Hello, how can I help you?
enable_auto_assignment: true
working_hours_enabled: true
timezone: America/New_York
channel:
line_channel_id: '1234567890'
line_channel_secret: line-channel-secret
line_channel_token: line-channel-token
telegram:
summary: Telegram channel settings
value:
name: Telegram Inbox
greeting_enabled: true
greeting_message: Hello, how can I help you?
enable_auto_assignment: true
working_hours_enabled: true
timezone: America/New_York
channel:
bot_token: 123456789:telegram-bot-token
whatsapp:
summary: WhatsApp channel settings
value:
name: WhatsApp Inbox
greeting_enabled: true
greeting_message: Hello, how can I help you?
enable_auto_assignment: true
working_hours_enabled: true
timezone: America/New_York
channel:
phone_number: '+15551234567'
provider: whatsapp_cloud
provider_config:
api_key: your-api-key
phone_number_id: your-phone-number-id
business_account_id: your-business-account-id
sms:
summary: SMS channel settings
value:
name: SMS Inbox
greeting_enabled: true
greeting_message: Hello, how can I help you?
enable_auto_assignment: true
working_hours_enabled: true
timezone: America/New_York
channel:
phone_number: '+15551234567'
provider_config:
api_key: your-api-key
api_secret: your-api-secret
application_id: your-application-id
account_id: your-account-id
responses:
'200':
description: Success
+1307 -80
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff