Merge branch 'develop' into feat/read-only-token
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 || '');
|
||||
|
||||
+29
-15
@@ -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
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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'
|
||||
@@ -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
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
Reference in New Issue
Block a user