| name | hebrew-chatbot-builder |
| description | Build conversational AI chatbots with native Hebrew support, including WhatsApp Business API integration, Telegram bot scaffolding, web chat widgets, Hebrew NLP patterns, and RTL chat UI components. Prevents common Hebrew chatbot mistakes like broken RTL alignment, incorrect gender inflections, and poor tokenization of prefixed prepositions that break intent detection. Use when user asks to "build a Hebrew chatbot", "integrate WhatsApp bot in Hebrew", "binui bot b'ivrit", or design conversation flows for Hebrew speakers. Covers intent detection for Hebrew morphology, entity extraction for Israeli data (NIS amounts, phone numbers, dates), and gender-aware responses. Do NOT use for non-Hebrew chatbots or general NLP pipelines without a Hebrew component. |
| license | MIT |
Hebrew Chatbot Builder
Build production-ready conversational AI chatbots with native Hebrew support. This skill covers platform integrations (WhatsApp, Telegram, web), Hebrew language patterns, RTL UI components, and conversation flow design for Hebrew speakers.
Instructions
Follow this procedure when building a Hebrew chatbot:
- Identify the platform/channel. Confirm whether the bot runs on WhatsApp Business API, Telegram, an embedded web widget, or several at once. Each has a different scaffold and message model.
- Confirm formal or informal register. Ask the user whether the audience expects informal Hebrew ("ืืืืจื", recommended for most consumer bots) or formal Hebrew (government, banking, legal). This decision shapes every response string.
- Ask the gender-handling strategy. Decide between asking the user's gender early and remembering it, using gender-neutral phrasing throughout, or slash notation ("ืืช/ื"). See the Gender-Aware Responses section.
- Scaffold via the relevant bundled script. Use
scripts/whatsapp-webhook-handler.py for WhatsApp or scripts/telegram-bot-scaffold.py for Telegram as the starting point, then adapt the Hebrew response templates and conversation flows.
- Wire entity extraction. Add
extract_israeli_entities() (or an equivalent) so the bot recognizes Israeli phone numbers, shekel amounts, dates, and Teudat Zehut from free-text Hebrew input.
- Verify RTL rendering. For web widgets, set
dir="rtl" on the outermost container and test on a real device. For WhatsApp and Telegram, verify Hebrew text and interactive buttons render correctly on both iOS and Android.
Examples
Example 1: WhatsApp order-status bot in Hebrew
User says: "Build a WhatsApp bot in Hebrew that lets customers check their order status."
Actions:
- Identify the channel: WhatsApp Business Cloud API.
- Confirm informal register (consumer audience) and gender-neutral phrasing for broad reach.
- Start from
scripts/whatsapp-webhook-handler.py, which already includes webhook verification, signature checks, and Hebrew response templates.
- Wire an interactive button menu ("ืืืืงืช ืืืื ื", "ืฉืืืืช ื ืคืืฆืืช", "ืืืจ/ื ืขื ื ืฆืื") and a
waiting_order_number state that validates the order number and looks it up.
- Submit a Hebrew
order_confirmation_he template via Meta Business Suite for proactive outbound updates.
Result: A working webhook handler that greets users in Hebrew, routes button replies, and answers order-status queries.
Example 2: Add an RTL Hebrew web chat widget to an existing site
User says: "Add a Hebrew chat widget to my website with proper RTL layout."
Actions:
- Identify the channel: embedded web widget.
- Confirm informal register and slash notation for CTAs.
- Use the RTL Chat Bubble Layout CSS and the
MessageBubble / detectDirection components from the Web Chat Widget section.
- Set
dir="rtl" on the outermost chat container element (not only in CSS) and add direction: rtl to the input field.
- Verify alignment in browser DevTools: user bubbles on the right, bot bubbles on the left, timestamps on the trailing side.
Result: A chat widget that renders Hebrew naturally, handles mixed Hebrew/English messages per-bubble, and does not break when a parent framework sets direction: ltr.
Hebrew Conversation Design
Formal vs Informal Register
Hebrew has distinct formal and informal registers. Choose based on your audience:
Informal (recommended for most consumer bots):
- Use second person singular: ืืช/ื (you)
- Shorter sentences
- Colloquial expressions: "ืื ืงืืจื?", "ืืื ืืขืื", "ืกืืื"
Formal (recommended for government, banking, legal):
- Use second person plural or passive voice
- Full sentences with proper grammar
- Formal expressions: "ืืืฆื ื ืืื ืืกืืืข?", "ืืืงืฉื ืืืชื/ื"
Gender-Aware Responses
Hebrew verbs and adjectives are gender-inflected. Handle this gracefully:
Strategy 1: Ask early and remember
Bot: "ืืื! ืืคื ื ืฉื ืชืืื, ืืื ืืคื ืืช ืืืื?"
Options: [ืืืจ] [ื ืงืื] [ืื ืืฉื ื ืื]
Strategy 2: Use gender-neutral phrasing
-- Instead of: "ืืชื/ืืช ืืืืื/ืืืืื ืช ืืืืฉืื"
-- Use: "ื ืืชื ืืืืฉืื" or "ืืคืฉืจ ืืืืฉืื"
-- Instead of: "ืจืืฆื/ืจืืฆื ืืจืืืช?"
-- Use: "ืืจืืืช ืขืื ืืคืฉืจืืืืช?"
Strategy 3: Slash notation (common in Israeli tech)
"ืืช/ื ืืืืื/ืช ืืืืืง ืืช ืืืคืฉืจืืืืช"
Hebrew Date/Time Formatting in Chat
RELATIVE_TIME_HE = {
"just_now": "ืขืืฉืื",
"minutes_ago": "ืืคื ื {n} ืืงืืช",
"hours_ago": "ืืคื ื {n} ืฉืขืืช",
"yesterday": "ืืชืืื",
"days_ago": "ืืคื ื {n} ืืืื",
"today": "ืืืื",
"tomorrow": "ืืืจ",
}
WhatsApp Business API Integration
Setup via Cloud API
The WhatsApp Cloud API (Meta's official API) is the recommended approach for Israeli businesses:
- Create a Meta Business Account at business.facebook.com
- Set up a WhatsApp Business Account in Meta Business Suite
- Create an App in Meta Developers and add WhatsApp product
- Get a phone number: Israeli numbers (+972) are supported
- Generate an access token for API calls
Message Templates (Hebrew)
WhatsApp requires pre-approved templates for outbound messages. Submit Hebrew templates via the Meta Business Suite:
import requests
def send_template_message(phone_number: str, template_data: dict):
"""Send a WhatsApp template message in Hebrew."""
url = f"https://graph.facebook.com/v25.0/{PHONE_NUMBER_ID}/messages"
payload = {
"messaging_product": "whatsapp",
"to": phone_number,
"type": "template",
"template": {
"name": "order_confirmation_he",
"language": {"code": "he"},
"components": [
{
"type": "body",
"parameters": [
{"type": "text", "text": template_data["customer_name"]},
{"type": "text", "text": template_data["order_id"]},
{"type": "text", "text": template_data["amount"]},
{"type": "text", "text": template_data["delivery_date"]},
]
}
]
}
}
headers = {
"Authorization": f"Bearer {ACCESS_TOKEN}",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
return response.json()
Interactive Messages
WhatsApp supports interactive buttons and lists, which work well with Hebrew:
def send_interactive_buttons(phone_number: str):
"""Send interactive buttons in Hebrew."""
payload = {
"messaging_product": "whatsapp",
"to": phone_number,
"type": "interactive",
"interactive": {
"type": "button",
"body": {
"text": "ืืื ืืคืฉืจ ืืขืืืจ ืื ืืืื?"
},
"action": {
"buttons": [
{
"type": "reply",
"reply": {"id": "check_order", "title": "ืืืืงืช ืืืื ื"}
},
{
"type": "reply",
"reply": {"id": "support", "title": "ืชืืืื ืืื ืืช"}
},
{
"type": "reply",
"reply": {"id": "hours", "title": "ืฉืขืืช ืคืขืืืืช"}
}
]
}
}
}
return send_whatsapp_message(payload)
def send_interactive_list(phone_number: str):
"""Send an interactive list in Hebrew."""
payload = {
"messaging_product": "whatsapp",
"to": phone_number,
"type": "interactive",
"interactive": {
"type": "list",
"body": {
"text": "ืืืจ/ื ืืช ืื ืืฉื ืฉืืขื ืืื ืืืชื:"
},
"action": {
"button": "ืืจืฉืืืช ืืืคืฉืจืืืืช",
"sections": [
{
"title": "ืฉืืจืืชืื",
"rows": [
{"id": "pricing", "title": "ืืืืจืื", "description": "ืฆืคืืื ืืืืืจืื ืขืืื ืืื"},
{"id": "catalog", "title": "ืงืืืื", "description": "ืขืืื ืืืืฆืจืื ืฉืื ื"},
{"id": "branches", "title": "ืกื ืืคืื", "description": "ืืฆืืืช ืืกื ืืฃ ืืงืจืื"}
]
},
{
"title": "ืชืืืื",
"rows": [
{"id": "faq", "title": "ืฉืืืืช ื ืคืืฆืืช", "description": "ืชืฉืืืืช ืืฉืืืืช ืฉืืืืืช"},
{"id": "human", "title": "ื ืฆืื ืื ืืฉื", "description": "ืฉืืื ืขื ื ืฆืื"}
]
}
]
}
}
}
return send_whatsapp_message(payload)
Webhook Handling
from flask import Flask, request, jsonify
import hmac
import hashlib
app = Flask(__name__)
VERIFY_TOKEN = "your_verify_token"
APP_SECRET = "your_app_secret"
@app.route("/webhook", methods=["GET"])
def verify_webhook():
"""Handle WhatsApp webhook verification."""
mode = request.args.get("hub.mode")
token = request.args.get("hub.verify_token")
challenge = request.args.get("hub.challenge")
if mode == "subscribe" and token == VERIFY_TOKEN:
return challenge, 200
return "Forbidden", 403
@app.route("/webhook", methods=["POST"])
def handle_webhook():
"""Process incoming WhatsApp messages."""
signature = request.headers.get("X-Hub-Signature-256", "")
body = request.get_data()
expected = "sha256=" + hmac.new(
APP_SECRET.encode(), body, hashlib.sha256
).hexdigest()
if not hmac.compare_digest(signature, expected):
return "Invalid signature", 403
data = request.get_json()
for entry in data.get("entry", []):
for change in entry.get("changes", []):
if change["field"] == "messages":
for message in change["value"].get("messages", []):
handle_incoming_message(message)
return jsonify({"status": "ok"}), 200
def handle_incoming_message(message: dict):
"""Process a single incoming message."""
sender = message["from"]
msg_type = message["type"]
if msg_type == "text":
text = message["text"]["body"]
process_hebrew_input(sender, text)
elif msg_type == "interactive":
if "button_reply" in message["interactive"]:
button_id = message["interactive"]["button_reply"]["id"]
handle_button_click(sender, button_id)
elif "list_reply" in message["interactive"]:
list_id = message["interactive"]["list_reply"]["id"]
handle_list_selection(sender, list_id)
Telegram Bot Integration
BotFather Setup
- Open @BotFather on Telegram
- Send
/newbot and follow prompts
- Set Hebrew description:
/setdescription then send Hebrew text
- Set Hebrew commands menu:
/setcommands
start - ืืชืื ืฉืืื
help - ืขืืจื
menu - ืชืคืจืื ืจืืฉื
order - ืืืื ื ืืืฉื
status - ืกืืืืก ืืืื ื
language - ืฉืคื / Language
Hebrew Inline Keyboards
from telegram import InlineKeyboardButton, InlineKeyboardMarkup, Update
from telegram.ext import Application, CommandHandler, CallbackQueryHandler
async def start(update: Update, context):
"""Send a Hebrew welcome message with inline keyboard."""
keyboard = [
[
InlineKeyboardButton("ืืืื ื ืืืฉื", callback_data="new_order"),
InlineKeyboardButton("ืืืืงืช ืกืืืืก", callback_data="check_status"),
],
[
InlineKeyboardButton("ืฉืืืืช ื ืคืืฆืืช", callback_data="faq"),
InlineKeyboardButton("ืืืจ/ื ืขื ื ืฆืื", callback_data="human_agent"),
],
[
InlineKeyboardButton("English", callback_data="lang_en"),
],
]
reply_markup = InlineKeyboardMarkup(keyboard)
await update.message.reply_text(
"ืฉืืื! ๐\n\n"
"ืื ื ืืืื ืฉื [ืฉื ืืขืกืง]. ืืื ืืคืฉืจ ืืขืืืจ?",
reply_markup=reply_markup,
)
async def button_handler(update: Update, context):
"""Handle inline keyboard button presses."""
query = update.callback_query
await query.answer()
if query.data == "new_order":
await query.edit_message_text("ืืขืืื! ืืื/ื ื ืชืืื ืืืื ื.\n\nืื ืชืจืฆื/ื ืืืืืื?")
elif query.data == "check_status":
await query.edit_message_text("ืฉืื/ื ืื ืืช ืืกืคืจ ืืืืื ื ืืืืืืง ืขืืืจื.")
elif query.data == "faq":
await show_faq(query)
elif query.data == "human_agent":
await query.edit_message_text(
"ืืขืืืจ ืืืชื ืื ืฆืื ืื ืืฉื.\n"
"ืฉืขืืช ืืคืขืืืืช ืฉืื ื: ื'-ื' 9:00-17:00\n"
"ื ืฆืื ืืืืืจ ืืืื ืืืงืื."
)
elif query.data == "lang_en":
await query.edit_message_text("Switching to English. How can I help you?")
app = Application.builder().token("YOUR_BOT_TOKEN").build()
app.add_handler(CommandHandler("start", start))
app.add_handler(CallbackQueryHandler(button_handler))
Group Bot Permissions
For Hebrew group bots, set appropriate permissions:
async def handle_message(update: Update, context):
chat_type = update.effective_chat.type
if chat_type in ("group", "supergroup"):
if update.message.text and (
update.message.text.startswith("/") or
f"@{context.bot.username}" in update.message.text
):
await process_group_command(update, context)
else:
await process_private_message(update, context)
Web Chat Widget
RTL Chat Bubble Layout
.chat-container {
direction: rtl;
display: flex;
flex-direction: column;
height: 100vh;
font-family: 'Heebo', 'Assistant', sans-serif;
}
.chat-messages {
flex: 1;
overflow-y: auto;
padding: 16px;
display: flex;
flex-direction: column;
gap: 8px;
}
.message-bubble {
max-width: 75%;
padding: 10px 14px;
border-radius: 16px;
font-size: 14px;
line-height: 1.6;
word-wrap: break-word;
}
.message-user {
align-self: flex-start;
background-color: #dcf8c6;
border-bottom-right-radius: 4px;
color: #111;
}
.message-bot {
align-self: flex-end;
background-color: #fff;
border-bottom-left-radius: 4px;
color: #111;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
}
.message-time {
font-size: 11px;
color: #999;
margin-top: 4px;
text-align: left;
}
.chat-input-container {
display: flex;
gap: 8px;
padding: 12px 16px;
border-top: 1px solid #e0e0e0;
background: #fff;
}
.chat-input {
flex: 1;
padding: 10px 14px;
border: 1px solid #ddd;
border-radius: 24px;
font-size: 14px;
direction: rtl;
text-align: right;
font-family: inherit;
}
.chat-input::placeholder {
color: #999;
text-align: right;
}
.chat-send-btn {
background: #25d366;
color: #fff;
border: none;
border-radius: 50%;
width: 40px;
height: 40px;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
transform: scaleX(-1);
}
Hebrew Typing Indicator
interface TypingIndicatorProps {
isTyping: boolean;
}
function TypingIndicator({ isTyping }: TypingIndicatorProps) {
if (!isTyping) return null;
return (
<div className="message-bubble message-bot typing-indicator">
<span className="typing-text">ืืงืืื/ื...</span>
<span className="typing-dots">
<span className="dot" />
<span className="dot" />
<span className="dot" />
</span>
</div>
);
}
Message Alignment Logic
function detectDirection(text: string): 'rtl' | 'ltr' {
const rtlRegex = /[\u0590-\u05FF\u0600-\u06FF\u0700-\u074F]/;
const ltrRegex = /[a-zA-Z]/;
for (const char of text) {
if (rtlRegex.test(char)) return 'rtl';
if (ltrRegex.test(char)) return 'ltr';
}
return 'rtl';
}
function MessageBubble({ text, sender }: { text: string; sender: 'user' | 'bot' }) {
const direction = detectDirection(text);
return (
<div
className={`message-bubble message-${sender}`}
style={{ direction, textAlign: direction === 'rtl' ? 'right' : 'left' }}
>
{text}
</div>
);
}
Hebrew NLP Patterns
Intent Detection
Hebrew morphological complexity makes intent detection challenging. Key strategies:
HEBREW_INTENTS = {
"greeting": [
"ืฉืืื", "ืืื", "ืื", "ืืืงืจ ืืื", "ืขืจื ืืื",
"ืื ืงืืจื", "ืื ื ืฉืืข", "ืืืื",
],
"farewell": [
"ืืื", "ืืืชืจืืืช", "ืฉืืื", "ืืื ืืื", "ืืืื ืืื",
"ืชืืื ืืืื", "ื ืชืจืื",
],
"help": [
"ืขืืจื", "ืื ื ืฆืจืื ืขืืจื", "ืื ื ืฆืจืืื ืขืืจื",
"ืชืขืืืจ ืื", "ืชืขืืจื ืื", "ืืื ืขืืฉืื", "ืื ืขืืฉืื",
],
"order_status": [
"ืืืคื ืืืืื ื", "ืกืืืืก ืืืื ื", "ืืชื ืืืืข",
"ืขืืืื ืืฉืืื", "ืืืืงืช ืืืื ื", "ืืกืคืจ ืืขืงื",
],
"complaint": [
"ืื ืืจืืฆื", "ืืขืื", "ืชืืื ื", "ืื ืขืืื",
"ืฉืืจืืช ืืจืืข", "ืจืืฆื ืืืชืืื ื", "ืงืืืืชื ืืืฆืจ ืคืืื",
],
"pricing": [
"ืืื ืขืืื", "ืื ืืืืืจ", "ืืืืจืื", "ืื ืื",
"ืืืฆืข", "ืืื ืืืชืจ", "ืืงืจ ืืื",
],
"human_agent": [
"ื ืฆืื", "ืืื ืืืืชื", "ืชืขืืืจ ืื ืฆืื",
"ืืืืจ ืขื ืืืฉืื", "ืื ืื", "ืื ื ืจืืฆื ืืืืจ ืขื ืื ืืื",
],
}
def detect_intent(text: str) -> tuple[str, float]:
"""Detect intent from Hebrew text using keyword matching.
For production, use an LLM or fine-tuned model instead."""
text_lower = text.strip()
best_intent = "unknown"
best_score = 0.0
for intent, phrases in HEBREW_INTENTS.items():
for phrase in phrases:
if phrase in text_lower:
score = len(phrase) / max(len(text_lower), 1)
if score > best_score:
best_score = score
best_intent = intent
return best_intent, min(best_score * 2, 1.0)
Caveats of naive substring matching (read before shipping):
- Nikud / vocalization: the keyword lists above assume unvocalized text. If user input (or your lists) contain nikud,
phrase in text fails. Strip nikud from both sides first, for example re.sub(r'[ึ-ื]', '', text).
- Prefix prepositions: Hebrew attaches ื/ื/ื/ื/ื/ื/ืฉ to the following word, so "ืืืืื ื" will not match the keyword "ืืืื ื". Naive
phrase in text silently misses these. For anything beyond a demo, use Hebrew NLP tooling that handles morphology, for example the YAP morphological analyzer (https://github.com/OnlpLab/yap) or Stanza's Hebrew pipeline, or move to an LLM (below).
LLM-based intent classification and response generation (recommended for production):
Substring matching breaks on paraphrases, typos, and morphology. For production, prompt an LLM to classify intent and draft the response. A compact pattern:
INTENT_SYSTEM_PROMPT = """ืืชื ืืกืืื ืืืื ืืช ืืฆ'ืืืืื ืฉืืจืืช ืืขืืจืืช.
ืืืืจ JSON ืืืื: {"intent": "<one of: greeting|farewell|help|order_status|complaint|pricing|human_agent|unknown>", "confidence": 0.0-1.0}.
ืืชืืฉื ืืืืจืคืืืืืื ืขืืจืืช (ืืืชืืืช ืฉืืืืฉ ื/ื/ื), ืฉืืืืืช ืืชืื ืืกืื ื."""
Guidance:
- Keep classification and response generation as two calls so you can log intent accuracy separately.
- Force JSON output and validate it; fall back to the keyword matcher if parsing fails.
- Models that handle Hebrew well (current as of mid-2026): Anthropic Claude (Sonnet/Opus tiers), the OpenAI GPT-5.x family, and Google Gemini 3.x. Model names and tiers change often (GPT-4o/4.1 and Gemini 2.x were retired in early 2026), so check each provider's current model list before pinning one.
- For a Hebrew-native option, evaluate Dicta-LM 3.0 (Dicta, Bar-Ilan University) for on-prem classification.
- Guard against prompt injection (OWASP LLM01). When free-text user input reaches an LLM that can trigger actions (order lookup, human handoff, ticket creation), treat it as untrusted: wrap the user text in clear delimiters, instruct the model to ignore any instructions inside it, never echo the system prompt, and validate the model's chosen action against an allowlist before executing it.
- Stream on the web widget, buffer on WhatsApp/Telegram. A web chat should stream tokens for low perceived latency; WhatsApp and Telegram cannot stream a partial message, so show a typing indicator and send the full reply once it is ready.
Entity Extraction
Extract Israeli-specific entities from Hebrew text:
import re
def extract_israeli_entities(text: str) -> dict:
"""Extract Israeli-specific entities from Hebrew text."""
entities = {}
phone_patterns = [
r'05\d[\s-]?\d{3}[\s-]?\d{4}',
r'0[2-9][\s-]?\d{3}[\s-]?\d{4}',
r'\+972[\s-]?\d{1,2}[\s-]?\d{3}[\s-]?\d{4}',
]
for pattern in phone_patterns:
matches = re.findall(pattern, text)
if matches:
entities.setdefault("phone_numbers", []).extend(matches)
nis_patterns = [
r'โช\s?[\d,]+(?:\.\d{2})?',
r'[\d,]+(?:\.\d{2})?\s?(?:โช|ืฉืงื|ืฉืงืืื|ืฉ"ื|ืฉื)',
]
for pattern in nis_patterns:
matches = re.findall(pattern, text)
if matches:
entities.setdefault("amounts_nis", []).extend(matches)
date_patterns = [
r'\d{1,2}[/.]\d{1,2}[/.]\d{2,4}',
r'\d{1,2}\s+ื?(?:ืื ืืืจ|ืคืืจืืืจ|ืืจืฅ|ืืคืจืื|ืืื|ืืื ื|ืืืื|ืืืืืกื|ืกืคืืืืจ|ืืืงืืืืจ|ื ืืืืืจ|ืืฆืืืจ)',
]
for pattern in date_patterns:
matches = re.findall(pattern, text)
if matches:
entities.setdefault("dates", []).extend(matches)
tz_pattern = r'(?<!\d)\d{9}(?!\d)'
tz_matches = [m for m in re.findall(tz_pattern, text) if is_valid_teudat_zehut(m)]
if tz_matches:
entities["teudat_zehut"] = tz_matches
email_pattern = r'[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}'
email_matches = re.findall(email_pattern, text)
if email_matches:
entities["emails"] = email_matches
return entities
def is_valid_teudat_zehut(value: str) -> bool:
"""Validate an Israeli Teudat Zehut using the official check-digit algorithm.
A 9-digit run is NOT a valid ID just because it has 9 digits. This applies
the Luhn-style weighting the Interior Ministry uses, so phone numbers and
random digit runs are rejected.
"""
digits = value.strip()
if len(digits) != 9 or not digits.isdigit():
return False
total = 0
for i, ch in enumerate(digits):
n = int(ch) * (1 if i % 2 == 0 else 2)
total += n if n < 10 else n - 9
return total % 10 == 0
Sentiment Analysis for Hebrew
HEBREW_SENTIMENT = {
"ืืขืืื": 1.0, "ืืฆืืื": 1.0, "ื ืืืจ": 0.9, "ืืืืชื": 0.9,
"ืืื": 0.6, "ืืคื": 0.6, "ื ืืื": 0.5, "ืืกืืจ": 0.3,
"ืชืืื": 0.4, "ืชืืื ืจืื": 0.7, "ืืืืืฅ": 0.8, "ืืืืืฆื": 0.8,
"ืืจืืฆื": 0.8, "ืฉืื": 0.7, "ืฉืืื": 0.7, "ืืืื": 0.8,
"ืกืืื": 0.5, "ืงืื": 0.5, "ืืืืื": 0.9,
"ืืจืืข": -0.9, "ื ืืจื": -0.9, "ืืืื": -1.0, "ืืืืื": -0.8,
"ืจืข": -0.7, "ืื ืืื": -0.6, "ืืขืื": -0.5, "ืชืงืื": -0.5,
"ืื ืขืืื": -0.7, "ืื ืืจืืฆื": -0.8, "ืืชืกืื": -0.7, "ืขืฆืื ื": -0.8,
"ืืจื": -1.0, "ืืื": -0.9, "ืืืฉื": -0.8, "ืืืืื": -0.8,
}
def analyze_hebrew_sentiment(text: str) -> dict:
"""Basic Hebrew sentiment analysis using lexicon matching."""
scores = []
words_found = []
sorted_expressions = sorted(HEBREW_SENTIMENT.keys(), key=len, reverse=True)
remaining_text = text
for expression in sorted_expressions:
if expression in remaining_text:
scores.append(HEBREW_SENTIMENT[expression])
words_found.append(expression)
remaining_text = remaining_text.replace(expression, "", 1)
if not scores:
return {"sentiment": "neutral", "score": 0.0, "words": []}
avg_score = sum(scores) / len(scores)
if avg_score > 0.2:
sentiment = "positive"
elif avg_score < -0.2:
sentiment = "negative"
else:
sentiment = "neutral"
return {
"sentiment": sentiment,
"score": round(avg_score, 2),
"words": words_found,
}
Conversation Flows
Hebrew Menu Tree
CONVERSATION_FLOWS = {
"main_menu": {
"message": "ืฉืืื! ืืื ืืคืฉืจ ืืขืืืจ?\nืืืจ/ื ืืืช ืืืืคืฉืจืืืืช:",
"options": {
"1": {"label": "ืืืื ื ืืืฉื", "next": "new_order"},
"2": {"label": "ืืืืงืช ืกืืืืก", "next": "check_status"},
"3": {"label": "ืฉืืืืช ื ืคืืฆืืช", "next": "faq"},
"4": {"label": "ืืืจ/ื ืขื ื ืฆืื", "next": "human_handoff"},
},
},
"new_order": {
"message": "ืืขืืื! ืืื/ื ื ืชืืื ืืืื ื.\nืื ืชืจืฆื/ื ืืืืืื?",
"type": "free_text",
"next": "order_quantity",
"back": "main_menu",
},
"order_quantity": {
"message": "ืืื ืืืืืืช?",
"type": "number",
"validation": {"min": 1, "max": 100},
"error": "ื ื ืืืืื ืืกืคืจ ืืื 1 ื-100",
"next": "order_confirm",
"back": "new_order",
},
"order_confirm": {
"message": "ืืกืืืื:\n{order_summary}\n\nืืืฉืจ ืืืื ื?",
"options": {
"1": {"label": "ืืืฉืืจ", "next": "order_complete"},
"2": {"label": "ืืืืื", "next": "main_menu"},
},
},
"order_complete": {
"message": "ืืืืื ื ืืืฆืขื ืืืฆืืื!\nืืกืคืจ ืืืื ื: {order_id}\nืชืืื ืืืื ืืื!",
"next": "main_menu",
},
"check_status": {
"message": "ืฉืื/ื ืื ืืช ืืกืคืจ ืืืืื ื (6 ืกืคืจืืช):",
"type": "pattern",
"pattern": r"^\d{6}$",
"error": "ืืกืคืจ ืืืื ื ืฆืจืื ืืืืื 6 ืกืคืจืืช. ื ืกื/ื ืฉืื:",
"next": "show_status",
"back": "main_menu",
},
"faq": {
"message": "ืฉืืืืช ื ืคืืฆืืช:\n\n1. ืฉืขืืช ืคืขืืืืช\n2. ืืืื ืืืช ืืืืจืืช\n3. ืืืืจื ืืฉืืื\n4. ืืืฆืขื ืชืฉืืื\n\nืืืจ/ื ื ืืฉื:",
"options": {
"1": {"label": "ืฉืขืืช ืคืขืืืืช", "response": "ืฉืขืืช ืคืขืืืืช:\nื'-ื': 9:00-17:00\nื': 9:00-13:00\nืฉืืช: ืกืืืจ"},
"2": {"label": "ืืืืจืืช", "response": "ื ืืชื ืืืืืืจ ืืืฆืจืื ืขื 14 ืืื ืืชืืจืื ืืจืืืฉื.\nืืฉ ืืืฆืื ืืฉืืื ืืช."},
"3": {"label": "ืืฉืืืืื", "response": "ืื ืื ื ืฉืืืืื ืืื ืืืจืฅ.\nืืฉืืื ืจืืื: 5-7 ืืื ืขืกืงืื.\nืืฉืืื ืืืืจ: 1-2 ืืื ืขืกืงืื."},
"4": {"label": "ืชืฉืืื", "response": "ืืืฆืขื ืชืฉืืื:\n- ืืจืืืก ืืฉืจืื (ืืืื, ืืืกืืจืงืืจื, ืืืงืก)\n- ืืื / ืคืืืืืงืก\n- ืืขืืจื ืื ืงืืืช\n- ืชืฉืืืืื (ืขื 12 ืชืฉืืืืื ืืื ืจืืืืช)"},
},
"back": "main_menu",
},
"human_handoff": {
"message": "ืืขืืืจ ืืืชื ืื ืฆืื ืื ืืฉื.\nืฉืขืืช ืคืขืืืืช: ื'-ื' 9:00-17:00.\n\nืืื ืชืืื, ืชืืจ/ื ืืงืฆืจื ืืช ืื ืืฉื ืฉืื ืืื ืฉื ืืื ืืขืืืจ ืืืจ ืืืชืจ:",
"type": "free_text",
"action": "create_support_ticket",
},
}
Fallback Handling in Hebrew
FALLBACK_RESPONSES = [
"ืื ืืฆืืืชื ืืืืื. ืืคืฉืจ ืื ืกื ืืืจืช?",
"ืกืืืื, ืื ืืื ืชื ืืช ืืืงืฉื. ื ืกื/ื ืฉืื ืื ืืงืื/ื 'ืชืคืจืื' ืืืคืฉืจืืืืช.",
"ืื ืืืื/ื ืฉืืื ืชื. ืืคืฉืจ ืืืืืจ ืืืืคืฉืจืืืืช ืื ืืืชืื 'ืขืืจื'.",
]
CONFUSED_AFTER_ATTEMPTS = (
"ื ืจืื ืฉืื ื ืืชืงืฉื ืืืืื. ืืื/ื ื ื ืกื ืืืจืช.\n"
"ืืงืื/ื ืืกืคืจ ืืืชืคืจืื, ืื 'ื ืฆืื' ืืฉืืื ืขื ืืื."
)
def get_fallback_response(attempt_count: int) -> str:
"""Get an appropriate fallback response based on attempt count."""
if attempt_count >= 3:
return CONFUSED_AFTER_ATTEMPTS
return FALLBACK_RESPONSES[attempt_count % len(FALLBACK_RESPONSES)]
Handoff to Human Agent
async def handoff_to_human(user_id: str, context: dict):
"""Transfer conversation to human agent."""
handoff_message = (
"ืชืืื ืขื ืืกืืื ืืช. ืืขืืืจ/ื ืืืชื ืื ืฆืื ืื ืืฉื.\n"
"ืืื ืืืชื ื ืืฉืืขืจ: {wait_time} ืืงืืช.\n\n"
"ืื ืื ืฉืืชืืช ืขื ืขืืฉืื ืืืขืืจ ืื ืฆืื."
)
ticket = {
"user_id": user_id,
"channel": context.get("channel", "web"),
"language": "he",
"conversation_history": context.get("history", []),
"detected_intent": context.get("last_intent", "unknown"),
"sentiment": context.get("sentiment", "neutral"),
"created_at": datetime.now().isoformat(),
}
await support_queue.add(ticket)
return handoff_message.format(
wait_time=await support_queue.estimated_wait()
)
Common Hebrew Chatbot Phrases
Essential Phrases
For a copy-paste set of Hebrew conversational phrases (greeting, confirmation, processing, success, error, fallback, goodbye, hold, apology) with transliterations and when to use each, see references/hebrew-chatbot-phrases.md.
Bundled Resources
This skill includes helper scripts in the scripts/ directory:
whatsapp-webhook-handler.py: Complete WhatsApp Cloud API webhook handler with signature verification, message routing, and Hebrew response templates
telegram-bot-scaffold.py: Telegram bot starter with Hebrew support, inline keyboards, conversation state management, and command handlers
And reference documents in references/:
hebrew-chatbot-phrases.md: Comprehensive Hebrew conversational phrases for bots, organized by category with transliterations
whatsapp-business-api-guide.md: Step-by-step WhatsApp Business API setup guide for Israeli businesses
Gotchas
- Hebrew chatbot responses must be RTL-aligned. Agents may generate HTML/CSS without dir="rtl" attributes, causing Hebrew text to align left and appear unnatural.
- Hebrew has gendered verb conjugations. A chatbot addressing a user should either use gender-neutral forms or ask for the user's preferred gender. Agents may default to masculine Hebrew forms.
- Common Hebrew greetings change by time of day: "ืืืงืจ ืืื" (morning), "ืฆืืจืืื ืืืืื" (noon), "ืขืจื ืืื" (evening). Agents may use a single greeting regardless of time.
- Hebrew word tokenization differs from English. Prefixed prepositions (ื-, ื-, ื-) are attached to the following word. Agents may split tokens incorrectly, breaking intent detection.
- Israeli users expect informal chatbot communication ("ืืืืจื"). Overly formal Hebrew sounds robotic and unnatural. Agents may generate formal Hebrew that alienates Israeli users.
Reference Links
Troubleshooting
WhatsApp webhook returns 403 after deployment
- Cause: The
X-Hub-Signature-256 verification is failing because APP_SECRET is not set or uses the wrong value. Meta signs webhook payloads with your App Secret, not the verify token.
- Solution: Set
WHATSAPP_APP_SECRET to the value from Meta Developers > App Settings > Basic > App Secret. Restart the server and verify it matches exactly (no trailing whitespace).
Hebrew text in WhatsApp template rejected by Meta
- Cause: Template body contains variables ({{1}}) that make up the entire message, or uses promotional language in a Utility-category template. Meta's review process flags these patterns.
- Solution: Ensure template text has substantial fixed content around variables. If rejected, check the rejection reason in Meta Business Suite > WhatsApp > Message Templates, adjust the wording, and resubmit.
Telegram bot ignores messages in groups
- Cause: Privacy mode is enabled by default in BotFather. In privacy mode, the bot only receives messages that start with
/ or mention the bot by @username.
- Solution: Either design your bot to work with commands only in groups (recommended), or disable privacy mode via BotFather:
/setprivacy > Disable. Note that disabling privacy means the bot receives all group messages.
RTL chat widget text aligns left despite direction: rtl
- Cause: A child element overrides the direction, or the CSS is applied to the wrong container. Common when using a UI framework that sets
direction: ltr at the body level.
- Solution: Set
dir="rtl" on the outermost chat container HTML element (not just CSS). Also add direction: rtl to the input field and any message bubble containers individually. Inspect with browser DevTools to find where the override occurs.