
Hebrew Chatbot Builder
- 52 installs
- 9 repo stars
- Updated August 3, 2026
- skills-il/developer-tools
Hebrew Chatbot Builder is an agent skill that scaffolds Hebrew-native AI chatbots with WhatsApp, Telegram, web widgets, RTL UI, and Hebrew NLP guardrails.
About
Hebrew Chatbot Builder is an agent skill for solo and indie builders who need AI assistants that sound natural in Hebrew across WhatsApp, Telegram, and embedded web chat. The package combines channel integration patterns, RTL-aware UI components, and Hebrew NLP conventions so agents do not ship broken right-to-left layouts, wrong gender agreement, or tokenizer failures on Hebrew prefix letters. Metadata positions it for Claude Code, Cursor, Codex, Windsurf, and related agents, with bilingual display copy for Israeli product teams. Use it during Build when you are wiring conversational flows to business APIs rather than when you are only doing English-only or generic LLM prompts. The bundled phrase reference accelerates onboarding flows, greetings, and support scripts without guessing colloquial Hebrew. Treat it as a specialized integration skill: skip it if your audience is not Hebrew-speaking, and pair it with your chosen model provider and hosting stack for production credentials and compliance.
- Native Hebrew conversational patterns with transliteration tables and usage notes for non-Hebrew-speaking builders
- WhatsApp Business API, Telegram bot, and web chat widget scaffolding in one Hebrew-focused package
- Hebrew NLP guidance and RTL chat UI components to avoid broken alignment and prefixed-preposition tokenization issues
- Guards against incorrect gender inflection and other common Hebrew UX mistakes in bots
- Explicitly not for non-Hebrew chatbots—scope is Israel-market Hebrew channels only
Hebrew Chatbot Builder by the numbers
- 52 all-time installs (skills.sh)
- Ranked #7,086 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Security screen: MEDIUM risk (skills.sh audit)
- Data as of Aug 4, 2026 (Skillselion catalog sync)
npx skills add https://github.com/skills-il/developer-tools --skill hebrew-chatbot-builderAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 52 |
|---|---|
| repo stars | ★ 9 |
| Security audit | 2 / 3 scanners passed |
| Last updated | August 3, 2026 |
| Repository | skills-il/developer-tools ↗ |
What it does
Scaffold Hebrew-first conversational bots for WhatsApp Business, Telegram, and on-site chat with correct RTL UI and NLP handling.
Who is it for?
Best when you're shipping Hebrew customer-support or lead bots for Israel on WhatsApp Business, Telegram, or embedded web chat.
Skip if: English-only or multilingual bots without a Hebrew-native requirement, or teams that only need generic LLM prompts with no channel or RTL work.
When should I use this skill?
You are building Hebrew-only conversational AI with WhatsApp Business API, Telegram, or web chat widgets and need RTL UI and Hebrew NLP patterns.
What you get
You get channel-ready Hebrew bot scaffolding, phrase references, and NLP/UI patterns so your agent can implement conversational flows without common Hebrew localization failures.
- Hebrew chatbot channel scaffolding for WhatsApp, Telegram, or web widget
- RTL chat UI components and Hebrew NLP pattern guidance
- Phrase reference tables with transliteration for bot copy
By the numbers
- Packaged metadata version 1.3.0 with bilingual he/en tags for chatbot, Hebrew, WhatsApp, Telegram, and Israel
- Lists seven supported agents including claude-code, cursor, codex, and windsurf
Files
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:
1. 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. 2. 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. 3. 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. 4. 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. 5. 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. 6. 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: 1. Identify the channel: WhatsApp Business Cloud API. 2. Confirm informal register (consumer audience) and gender-neutral phrasing for broad reach. 3. Start from scripts/whatsapp-webhook-handler.py, which already includes webhook verification, signature checks, and Hebrew response templates. 4. Wire an interactive button menu ("בדיקת הזמנה", "שאלות נפוצות", "דבר/י עם נציג") and a waiting_order_number state that validates the order number and looks it up. 5. 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: 1. Identify the channel: embedded web widget. 2. Confirm informal register and slash notation for CTAs. 3. Use the RTL Chat Bubble Layout CSS and the MessageBubble / detectDirection components from the Web Chat Widget section. 4. Set dir="rtl" on the outermost chat container element (not only in CSS) and add direction: rtl to the input field. 5. 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
# Date formats for chat messages
# Israeli standard: DD/MM/YYYY or DD.MM.YYYY
# In conversation: "יום שלישי, 14 במרץ" (Tuesday, March 14)
# Time format: 24-hour clock is standard in Israel
# "בשעה 14:30" (at 14:30), not "2:30 PM"
# Relative time in Hebrew
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:
1. Create a Meta Business Account at business.facebook.com 2. Set up a WhatsApp Business Account in Meta Business Suite 3. Create an App in Meta Developers and add WhatsApp product 4. Get a phone number: Israeli numbers (+972) are supported 5. 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:
# Template example: Order confirmation
# Template name: order_confirmation_he
# Language: he
# Body: "שלום {{1}}, ההזמנה שלך מספר {{2}} התקבלה בהצלחה. סכום: ₪{{3}}. צפי למשלוח: {{4}}."
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, # E.164 format: 972501234567
"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."""
# Verify signature
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"] # Phone number
msg_type = message["type"]
if msg_type == "text":
text = message["text"]["body"]
# Process Hebrew text
process_hebrew_input(sender, text)
elif msg_type == "interactive":
# Handle button/list replies
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
1. Open @BotFather on Telegram 2. Send /newbot and follow prompts 3. Set Hebrew description: /setdescription then send Hebrew text 4. Set Hebrew commands menu:
/setcommands
start - התחל שיחה
help - עזרה
menu - תפריט ראשי
order - הזמנה חדשה
status - סטטוס הזמנה
language - שפה / LanguageHebrew 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() # Acknowledge the button press
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?")
# Build the application
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:
# In BotFather:
# /setjoingroups - Enable/disable joining groups
# /setprivacy - Set privacy mode (recommended: enabled, bot only sees commands)
# Handle group messages differently
async def handle_message(update: Update, context):
chat_type = update.effective_chat.type
if chat_type in ("group", "supergroup"):
# In groups, only respond to commands or mentions
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:
# In private chat, respond to all messages
await process_private_message(update, context)Web Chat Widget
RTL Chat Bubble Layout
/* RTL Chat Container */
.chat-container {
direction: rtl;
display: flex;
flex-direction: column;
height: 100vh;
font-family: 'Heebo', 'Assistant', sans-serif;
}
/* Message area */
.chat-messages {
flex: 1;
overflow-y: auto;
padding: 16px;
display: flex;
flex-direction: column;
gap: 8px;
}
/* Message bubble base */
.message-bubble {
max-width: 75%;
padding: 10px 14px;
border-radius: 16px;
font-size: 14px;
line-height: 1.6;
word-wrap: break-word;
}
/* User message (right side in RTL) */
.message-user {
align-self: flex-start; /* In RTL, flex-start is right */
background-color: #dcf8c6;
border-bottom-right-radius: 4px; /* Tail on right for RTL */
color: #111;
}
/* Bot message (left side in RTL) */
.message-bot {
align-self: flex-end; /* In RTL, flex-end is left */
background-color: #fff;
border-bottom-left-radius: 4px; /* Tail on left for RTL */
color: #111;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
}
/* Timestamp */
.message-time {
font-size: 11px;
color: #999;
margin-top: 4px;
text-align: left; /* Time on the trailing side */
}
/* Input area */
.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;
/* Flip send icon for RTL */
transform: scaleX(-1);
}Hebrew Typing Indicator
// React component for 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>
);
}
// CSS for typing animation
/*
.typing-dots {
display: inline-flex;
gap: 4px;
margin-right: 8px;
}
.typing-dots .dot {
width: 6px;
height: 6px;
background: #999;
border-radius: 50%;
animation: typing-bounce 1.4s infinite ease-in-out;
}
.typing-dots .dot:nth-child(1) { animation-delay: 0s; }
.typing-dots .dot:nth-child(2) { animation-delay: 0.2s; }
.typing-dots .dot:nth-child(3) { animation-delay: 0.4s; }
@keyframes typing-bounce {
0%, 60%, 100% { transform: translateY(0); }
30% { transform: translateY(-6px); }
}
*/Message Alignment Logic
// Determine text direction for mixed Hebrew/English messages
function detectDirection(text: string): 'rtl' | 'ltr' {
// Check first strong directional character
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'; // Default to RTL for Hebrew-first apps
}
// Apply to message bubble
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:
# Common Hebrew intents with example phrases
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:
# Longer phrase match = higher confidence
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) # Normalize scoreCaveats 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 textfails. Strip nikud from both sides first, for examplere.sub(r'[֑-ׇ]', '', text). - Prefix prepositions: Hebrew attaches ב/ל/מ/ה/ו/כ/ש to the following word, so "בהזמנה" will not match the keyword "הזמנה". Naive
phrase in textsilently 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:
# Pseudocode - call your provider's SDK (Anthropic, OpenAI, or Google).
INTENT_SYSTEM_PROMPT = """אתה מסווג כוונות לצ'אטבוט שירות בעברית.
החזר JSON בלבד: {"intent": "<one of: greeting|farewell|help|order_status|complaint|pricing|human_agent|unknown>", "confidence": 0.0-1.0}.
התחשב במורפולוגיה עברית (אותיות שימוש ב/ל/מ), שגיאות כתיב וסלנג."""
# 1. Classification turn: send INTENT_SYSTEM_PROMPT + the user message, parse the JSON.
# 2. Response turn: send a separate system prompt with the detected intent,
# the conversation history, and the register/gender decisions, ask for the Hebrew reply.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 = {}
# Israeli phone numbers
phone_patterns = [
r'05\d[\s-]?\d{3}[\s-]?\d{4}', # Mobile: 050-1234567
r'0[2-9][\s-]?\d{3}[\s-]?\d{4}', # Landline: 02-1234567
r'\+972[\s-]?\d{1,2}[\s-]?\d{3}[\s-]?\d{4}', # International
]
for pattern in phone_patterns:
matches = re.findall(pattern, text)
if matches:
entities.setdefault("phone_numbers", []).extend(matches)
# NIS amounts (shekel)
nis_patterns = [
r'₪\s?[\d,]+(?:\.\d{2})?', # ₪100 or ₪1,000.00
r'[\d,]+(?:\.\d{2})?\s?(?:₪|שקל|שקלים|ש"ח|שח)', # 100 שקל
]
for pattern in nis_patterns:
matches = re.findall(pattern, text)
if matches:
entities.setdefault("amounts_nis", []).extend(matches)
# Israeli dates (DD/MM/YYYY or DD.MM.YYYY)
date_patterns = [
r'\d{1,2}[/.]\d{1,2}[/.]\d{2,4}', # 14/03/2025
r'\d{1,2}\s+ב?(?:ינואר|פברואר|מרץ|אפריל|מאי|יוני|יולי|אוגוסט|ספטמבר|אוקטובר|נובמבר|דצמבר)',
]
for pattern in date_patterns:
matches = re.findall(pattern, text)
if matches:
entities.setdefault("dates", []).extend(matches)
# Teudat Zehut (9 digits, standalone)
# NOTE: this regex matches ANY 9-digit run with no check-digit validation.
# It will accept phone numbers, order IDs, and typos. Validate matches
# with is_valid_teudat_zehut() below before treating them as a real ID.
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 addresses
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 == 0Sentiment Analysis for Hebrew
# Simple Hebrew sentiment lexicon (for basic analysis)
# For production, use an LLM or Hebrew-trained model
HEBREW_SENTIMENT = {
# Positive
"מעולה": 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,
# Negative
"גרוע": -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 = []
# Check multi-word expressions first (sorted by length, longest first)
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 state machine for Hebrew chatbot
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"
"כל מה שכתבת עד עכשיו יועבר לנציג."
)
# Create support ticket with conversation history
ticket = {
"user_id": user_id,
"channel": context.get("channel", "web"), # whatsapp/telegram/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(),
}
# Queue for human agent
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 templatestelegram-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 transliterationswhatsapp-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
| Source | URL | What to Check |
|---|---|---|
| WhatsApp Cloud API Docs | https://developers.facebook.com/documentation/business-messaging/whatsapp/about-the-platform | API version, message types, webhook format |
| Meta Graph API Changelog | https://developers.facebook.com/docs/graph-api/changelog/ | Latest API version, breaking changes |
| Telegram Bot API Docs | https://core.telegram.org/bots/api | Bot API methods, inline keyboards, webhook setup |
| python-telegram-bot Docs | https://docs.python-telegram-bot.org/ | Library version, async API changes |
| Meta Business Suite | https://business.facebook.com/ | Template creation, phone number setup |
Troubleshooting
WhatsApp webhook returns 403 after deployment
- Cause: The
X-Hub-Signature-256verification is failing becauseAPP_SECRETis not set or uses the wrong value. Meta signs webhook payloads with your App Secret, not the verify token. - Solution: Set
WHATSAPP_APP_SECRETto 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: ltrat the body level. - Solution: Set
dir="rtl"on the outermost chat container HTML element (not just CSS). Also adddirection: rtlto the input field and any message bubble containers individually. Inspect with browser DevTools to find where the override occurs.
{
"schemaVersion": "1.0",
"skill": "hebrew-chatbot-builder",
"generated_at": "2026-06-21T00:00:00Z",
"claims": [
{
"claim_id": "llm-models-2026",
"claim": "As of mid-2026, LLMs that handle Hebrew well include Anthropic Claude (Sonnet/Opus), the OpenAI GPT-5.x family, and Google Gemini 3.x. GPT-4o/4.1 and Gemini 2.x were retired in early 2026. Dicta-LM 3.0 is the current Hebrew-native option.",
"source_url": "https://help.openai.com/en/articles/9624314-model-release-notes",
"raw_snippet": "GPT-4o and GPT-4.1 were retired from ChatGPT in early 2026; the current lineup is the GPT-5.x family.",
"fetched_at": "2026-06-21T00:00:00Z",
"appears_in": ["SKILL.md", "SKILL_HE.md"]
},
{
"claim_id": "whatsapp-graph-v25",
"claim": "The WhatsApp Cloud API uses Graph API v25.0; messages are sent via POST https://graph.facebook.com/v25.0/{PHONE_NUMBER_ID}/messages.",
"source_url": "https://developers.facebook.com/docs/graph-api/changelog/",
"raw_snippet": "Graph API v25.0 is the current version. WhatsApp Cloud API endpoints are versioned under graph.facebook.com.",
"fetched_at": "2026-06-21T00:00:00Z",
"appears_in": ["SKILL.md", "SKILL_HE.md", "references/whatsapp-business-api-guide.md", "scripts/whatsapp-webhook-handler.py"]
},
{
"claim_id": "whatsapp-platform-docs",
"claim": "WhatsApp Cloud API platform documentation (setup, message types, webhooks) lives on Meta's business-messaging docs.",
"source_url": "https://developers.facebook.com/documentation/business-messaging/whatsapp/about-the-platform",
"raw_snippet": "The WhatsApp Business Platform Cloud API lets businesses send and receive messages; docs cover API version, message types, and webhook format.",
"fetched_at": "2026-06-21T00:00:00Z",
"appears_in": ["SKILL.md", "SKILL_HE.md"]
},
{
"claim_id": "whatsapp-pricing",
"claim": "WhatsApp moved to per-message pricing on 1 July 2025; per-message rates by country and category change regularly, so do not hardcode them.",
"source_url": "https://developers.facebook.com/documentation/business-messaging/whatsapp/pricing",
"raw_snippet": "WhatsApp Business pricing is per message as of July 1, 2025; rates vary by country and category.",
"fetched_at": "2026-06-21T00:00:00Z",
"appears_in": ["SKILL.md", "references/whatsapp-business-api-guide.md"]
},
{
"claim_id": "telegram-bot-api",
"claim": "The Telegram Bot API is documented at core.telegram.org/bots/api; python-telegram-bot (>=21, async) wraps it.",
"source_url": "https://core.telegram.org/bots/api",
"raw_snippet": "The Bot API is an HTTP-based interface for building bots for Telegram.",
"fetched_at": "2026-06-21T00:00:00Z",
"appears_in": ["SKILL.md", "SKILL_HE.md", "scripts/telegram-bot-scaffold.py"]
},
{
"claim_id": "python-telegram-bot",
"claim": "python-telegram-bot v20+/21+ uses an async API; it is the standard Python Telegram bot library.",
"source_url": "https://docs.python-telegram-bot.org/",
"raw_snippet": "python-telegram-bot provides a pure Python, asynchronous interface for the Telegram Bot API.",
"fetched_at": "2026-06-21T00:00:00Z",
"appears_in": ["SKILL.md", "scripts/telegram-bot-scaffold.py"]
},
{
"claim_id": "yap-hebrew-nlp",
"claim": "YAP (Yet Another Parser, OnlpLab) is a Hebrew morphological analyzer/parser usable for Hebrew NLP.",
"source_url": "https://github.com/OnlpLab/yap",
"raw_snippet": "YAP is a morphological analyzer and dependency parser for Hebrew from the Open University NLP Lab.",
"fetched_at": "2026-06-21T00:00:00Z",
"appears_in": ["SKILL.md", "references/whatsapp-business-api-guide.md"]
},
{
"claim_id": "botfather-commands",
"claim": "Telegram bots are created and configured via BotFather commands: /newbot (create), /setdescription, /setprivacy.",
"source_url": "https://core.telegram.org/bots/api",
"raw_snippet": "Use BotFather's /newbot to create a bot, /setdescription to set its description, and /setprivacy to control whether the bot sees all group messages or only commands.",
"fetched_at": "2026-06-21T00:00:00Z",
"appears_in": ["SKILL.md", "SKILL_HE.md", "scripts/telegram-bot-scaffold.py"]
},
{
"claim_id": "placeholder-webhook-url",
"claim": "your-domain.com/webhook is an illustrative placeholder for the operator's own webhook callback URL.",
"source_url": "https://your-domain.com/webhook",
"raw_snippet": "Replace your-domain.com/webhook with your own HTTPS webhook endpoint registered with the WhatsApp/Telegram platform.",
"fetched_at": "2026-06-21T00:00:00Z",
"appears_in": ["SKILL.md", "references/whatsapp-business-api-guide.md"]
},
{
"claim_id": "placeholder-tracking-url",
"claim": "tracking.example.com with a {{1}} placeholder illustrates a WhatsApp template button URL with a dynamic parameter; example.com is a reserved documentation domain.",
"source_url": "https://tracking.example.com/{{1}}]",
"raw_snippet": "WhatsApp template buttons can carry a dynamic URL parameter {{1}}; tracking.example.com is an illustrative placeholder host.",
"fetched_at": "2026-06-21T00:00:00Z",
"appears_in": ["references/whatsapp-business-api-guide.md"]
},
{
"claim_id": "meta-business-manager",
"claim": "WhatsApp Business accounts are managed in Meta Business Manager.",
"source_url": "https://business.facebook.com/",
"raw_snippet": "Meta Business Manager (business.facebook.com) is where WhatsApp Business accounts, phone numbers, and message templates are managed.",
"fetched_at": "2026-06-21T00:00:00Z",
"appears_in": ["references/whatsapp-business-api-guide.md"]
},
{
"claim_id": "graph-messages-endpoint-shellvar",
"claim": "The WhatsApp send-message endpoint with a shell/env variable for the phone number id.",
"source_url": "https://graph.facebook.com/v25.0/${PHONE_NUMBER_ID}/messages",
"raw_snippet": "POST https://graph.facebook.com/v25.0/{PHONE_NUMBER_ID}/messages sends a WhatsApp message; PHONE_NUMBER_ID is substituted at runtime.",
"fetched_at": "2026-06-21T00:00:00Z",
"appears_in": ["references/whatsapp-business-api-guide.md"]
}
]
}
{
"author": "skills-il",
"version": "1.4.0",
"category": "developer-tools",
"tags": {
"he": [
"צ'אטבוט",
"עברית",
"וואטסאפ",
"טלגרם",
"ישראל"
],
"en": [
"chatbot",
"hebrew",
"whatsapp",
"telegram",
"israel"
]
},
"display_name": {
"he": "בונה צ'אטבוט בעברית",
"en": "Hebrew Chatbot Builder"
},
"display_description": {
"he": "בונים צ'אטבוטים של AI עם תמיכה מקורית בעברית: WhatsApp Business API, בוט טלגרם, ווידג'ט צ'אט לאתר, NLP בעברית ורכיבי UI ב-RTL. מונע שגיאות נפוצות כמו RTL שבור, הטיות מגדר וטוקניזציה לקויה של אותיות שימוש. אל תשתמשו לצ'אטבוטים בלי רכיב עברית.",
"en": "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. Do NOT use for non-Hebrew chatbots."
},
"supported_agents": [
"claude-code",
"cursor",
"github-copilot",
"windsurf",
"opencode",
"codex",
"gemini-cli"
]
}
{
"cycles": [
{
"version": "1.4.0",
"date": "2026-06-21",
"lessons": [
"STALE fixes: LLM model lineup refreshed (GPT-4o/4.1 retired -> GPT-5.x; Gemini 2.x -> 3.x; DictaLM -> Dicta-LM 3.0). Updated 2 Meta WhatsApp doc URLs from the deprecated /docs/whatsapp/* paths to the current /documentation/business-messaging/whatsapp/* paths. WhatsApp Graph API v25.0 verified current; python-telegram-bot async API current.",
"Coverage (CRITICAL) added concisely (file was near the word cap): prompt-injection guardrail (OWASP LLM01) for free-text -> action flows, and stream-on-web / buffer-on-WhatsApp-Telegram. Trimmed the redundant in-body Essential Phrases table to a pointer (it duplicated references/hebrew-chatbot-phrases.md) which also fixed an EN/HE heading-level parity drift.",
"Deferred (MAJOR, dedicated next cycle, needs trims to fit cap): RAG over Hebrew docs + Hebrew embedding model guidance (e.g. Cohere multilingual); tool/function-calling pattern to replace the hand-rolled classify-then-respond; voice/STT-TTS for Hebrew (Whisper large-v3 / ivrit.ai) since WhatsApp+Telegram deliver voice notes; an eval harness for Hebrew intent accuracy. Deferred (MINOR): Hebrew tokenization cost note; LRM/RLM bidi controls for embedding LTR runs; don't-log-raw-Teudat-Zehut (Amendment 13)."
]
}
]
}
Hebrew Chatbot Phrases Reference
A comprehensive collection of Hebrew conversational phrases for chatbots, organized by category. Each phrase includes transliteration for non-Hebrew speakers and usage notes.
Greetings
| Hebrew | Transliteration | Usage |
|---|---|---|
| שלום! | Shalom! | Universal greeting, formal and informal |
| היי! | Hai! | Casual greeting |
| אהלן! | Ahlan! | Casual greeting (from Arabic) |
| בוקר טוב! | Boker tov! | Good morning |
| צהריים טובים! | Tsohorayim tovim! | Good afternoon |
| ערב טוב! | Erev tov! | Good evening |
| שלום, איך אפשר לעזור? | Shalom, eikh efshar la'azor? | Standard bot opening |
| ברוכים הבאים! | Brukhim ha'ba'im! | Welcome (formal, gender-neutral plural) |
| ברוך/ה הבא/ה! | Barukh/a ha'ba/a! | Welcome (gendered singular) |
Asking How to Help
| Hebrew | Transliteration | Usage |
|---|---|---|
| איך אפשר לעזור? | Eikh efshar la'azor? | Neutral, most common |
| במה אוכל לסייע? | Ba'ma ukhal lesaye'a? | Formal |
| מה מעניין אותך? | Ma me'anyen otkha? | Casual (masc.) |
| מה מעניינת אותך? | Ma me'anyenet otakh? | Casual (fem.) |
| צריך/ה עוד משהו? | Tsarikh/a od mashehu? | After completing a task |
| יש עוד משהו שאני יכול/ה לעזור בו? | Yesh od mashehu she'ani yakhol/a la'azor bo? | After completing a task (fuller) |
Confirmations
| Hebrew | Transliteration | Usage |
|---|---|---|
| מעולה, הבנתי | Me'ule, hevanti | Acknowledged input |
| הבנתי, רגע | Hevanti, rega | Acknowledged, processing |
| קיבלתי, טיפול | Kibalti, tipul | Received, handling it |
| בוצע בהצלחה! | Butza be'hatzlakha! | Action completed successfully |
| הפעולה הושלמה | Ha'pe'ula hushlema | Action completed (formal) |
| נרשם! | Nirsham! | Registered/noted |
| מצוין! | Metzuyan! | Excellent (positive reinforcement) |
| סבבה | Sababa | OK/Cool (very casual) |
| בסדר גמור | Be'seder gamur | Absolutely fine |
Processing / Waiting
| Hebrew | Transliteration | Usage |
|---|---|---|
| רגע, בודק/ת... | Rega, bodek/et... | Checking something |
| ממתין/ה לתגובתך | Mamtin/a le'tguvatekha | Waiting for user input |
| מעבד/ת את הבקשה... | Me'abed/et et ha'bakasha... | Processing request |
| עוד רגע קט... | Od rega kat... | Just a moment |
| מחפש/ת... | Mekhapes/et... | Searching |
| כמעט מוכן... | Kim'at mukhan... | Almost ready |
Error Messages
| Hebrew | Transliteration | Usage |
|---|---|---|
| משהו השתבש, נסה/י שוב | Mashehu hishtabesh, nase/i shuv | Generic error |
| לא הצלחתי להבין | Lo hitzlakhti lehavin | Didn't understand |
| אפשר לנסח אחרת? | Efshar lenase'akh akheret? | Ask to rephrase |
| הקלט לא תקין | Ha'kelet lo takin | Invalid input |
| נסה/י שוב מאוחר יותר | Nase/i shuv me'ukhar yoter | Try again later |
| הפעולה נכשלה | Ha'pe'ula nikhshela | Action failed (formal) |
| אופס, נתקלנו בבעיה | Oops, nitkalna be'be'aya | Oops, we hit a problem (casual) |
| סליחה, לא הצלחנו לעבד את הבקשה | Slikha, lo hitzlakhnu le'abed et ha'bakasha | Sorry, couldn't process (formal) |
Not Understood / Fallback
| Hebrew | Transliteration | Usage |
|---|---|---|
| לא הצלחתי להבין. אפשר לנסח אחרת? | Lo hitzlakhti lehavin. Efshar lenase'akh akheret? | First attempt |
| סליחה, לא הבנתי. נסה/י שוב או הקלד/י 'תפריט' | Slikha, lo hevanti. Nase/i shuv o hakled/i 'tafrit' | Second attempt |
| נראה שאני מתקשה להבין. הקלד/י 'נציג' לשיחה עם אדם | Nir'e she'ani mitkashe lehavin. Hakled/i 'natsig' le'sikha im adam | Third attempt |
Goodbye / Closing
| Hebrew | Transliteration | Usage |
|---|---|---|
| תודה ויום טוב! | Toda ve'yom tov! | Standard closing |
| להתראות! | Le'hitra'ot! | See you! |
| שיהיה לך/לך יום נפלא! | She'yihiye lekha/lakh yom nifla! | Have a wonderful day (masc./fem.) |
| תודה שפנית אלינו | Toda she'panita eleinu | Thank you for contacting us (masc.) |
| תודה שפנית אלינו | Toda she'panit eleinu | Thank you for contacting us (fem.) |
| נשמח לעזור שוב בכל עת | Nismakh la'azor shuv be'khol et | Happy to help again anytime |
| שבוע טוב! | Shavu'a tov! | Have a good week (common Sun-Thu) |
| שבת שלום! | Shabbat shalom! | Good Shabbat (Friday) |
| חג שמח! | Khag same'akh! | Happy holiday |
Apologies
| Hebrew | Transliteration | Usage |
|---|---|---|
| מצטער/ת על אי הנוחות | Mitztaer/et al i ha'nokhiyut | Sorry for the inconvenience |
| סליחה על העיכוב | Slikha al ha'ikuv | Sorry for the delay |
| מתנצלים על התקלה | Mitnatslim al ha'takala | We apologize for the issue |
| אנחנו עובדים על פתרון | Anakhnu ovdim al pitaron | We're working on a solution |
Requesting Information
| Hebrew | Transliteration | Usage |
|---|---|---|
| מה השם שלך? | Ma ha'shem shelkha/shelakh? | What is your name? |
| מה מספר הטלפון שלך? | Ma mispar ha'telefon shelkha? | What is your phone number? |
| מה כתובת המייל שלך? | Ma ktovet ha'mail shelkha? | What is your email? |
| שלח/י לי את מספר ההזמנה | Shlakh/i li et mispar ha'hazmana | Send me the order number |
| באיזה עיר את/ה? | Be'eizo ir at/a? | Which city are you in? |
Confirmations / Yes-No
| Hebrew | Transliteration | Usage |
|---|---|---|
| בטוח? | Batu'akh? | Are you sure? (masc.) |
| בטוחה? | Betukha? | Are you sure? (fem.) |
| לאשר? | Le'asher? | Confirm? |
| כן, לאשר | Ken, le'asher | Yes, confirm |
| לא, לבטל | Lo, levatel | No, cancel |
| נכון? | Nakhon? | Right? / Correct? |
Business Hours
| Hebrew | Transliteration | Usage |
|---|---|---|
| שעות פעילות | She'ot pe'ilut | Business hours (header) |
| ימים א'-ה' | Yamim alef-he | Sunday through Thursday |
| יום ו' | Yom vav | Friday |
| שבת | Shabbat | Saturday |
| סגור | Sagur | Closed |
| פתוח | Patu'akh | Open |
| אנחנו זמינים בשעות... | Anakhnu zminim be'sha'ot... | We're available during... |
Payment Related
| Hebrew | Transliteration | Usage |
|---|---|---|
| סכום לתשלום | Skhum le'tashlum | Amount to pay |
| תשלום בוצע | Tashlum butza | Payment completed |
| תשלום נכשל | Tashlum nikhshal | Payment failed |
| כרטיס אשראי | Kartis ashrai | Credit card |
| העברה בנקאית | Ha'avara bankai't | Bank transfer |
| תשלומים | Tashlumim | Installments |
| חשבונית | Kheshbonit | Invoice |
| קבלה | Kabala | Receipt |
Status Updates
| Hebrew | Transliteration | Usage |
|---|---|---|
| ההזמנה התקבלה | Ha'hazmana hitkabla | Order received |
| ההזמנה בטיפול | Ha'hazmana be'tipul | Order being processed |
| ההזמנה נשלחה | Ha'hazmana nishlekha | Order shipped |
| המשלוח בדרך אליך | Ha'mishlo'akh ba'derekh eleikha | Delivery on its way |
| ההזמנה הגיעה | Ha'hazmana higi'a | Order delivered |
| ההזמנה בוטלה | Ha'hazmana butla | Order cancelled |
Tips for Natural Hebrew in Chatbots
1. Avoid literal translations from English. Hebrew has its own natural phrasing patterns. "How can I help you?" translates naturally to "איך אפשר לעזור?" not "כיצד אני יכול לעזור לך?"
2. Use gender-neutral phrasing when possible. Instead of "אתה רוצה / את רוצה", use "לראות עוד אפשרויות?"
3. Keep it short. Israeli Hebrew communication tends to be direct and concise. Avoid long, flowery sentences.
4. Use slash notation sparingly. "שלח/י" (send, masc./fem.) is common but can get tiring if overused. Mix with neutral phrasing.
5. Casual is usually better. Unless it's a government or banking bot, Israeli users prefer casual register. Use "היי" over "שלום כבוד הלקוח".
6. Hebrew numbers. In chat, use Western numerals (1, 2, 3), not Hebrew letters for numbers. Date format: DD/MM/YYYY.
7. Emoji usage. Israeli users are comfortable with emojis in casual contexts. Use sparingly in formal bots.
8. Common abbreviations:
- ת"ז = תעודת זהות (ID card)
- ת"ש = תשלומים (installments)
- ש"ח = שקלים חדשים (NIS)
- טל' = טלפון (phone)
WhatsApp Business API Setup Guide for Israeli Businesses
Overview
This guide covers setting up the WhatsApp Business Cloud API for Israeli businesses. The Cloud API is Meta's hosted solution (no need for on-premise servers), and is the recommended approach for new integrations.
Prerequisites
1. Meta Business Account (business.facebook.com) 2. Israeli phone number (+972) that can receive SMS or voice calls for verification 3. Business verification on Meta Business Suite (requires Israeli business registration documents) 4. SSL-enabled webhook endpoint (HTTPS required)
Step-by-Step Setup
1. Create Meta Business Account
1. Go to business.facebook.com 2. Create an account using your Israeli business details 3. Complete business verification:
- Upload Israeli business registration certificate (תעודת רישום עסק)
- Provide business address in Israel
- Submit utility bill or bank statement as address proof
- Verification typically takes 2-5 business days
2. Set Up WhatsApp Business
1. Go to Meta Developers (developers.facebook.com) 2. Create a new app (type: "Business") 3. Add the "WhatsApp" product to your app 4. In WhatsApp settings, link your Meta Business Account
3. Phone Number Registration
Register your Israeli phone number:
1. Navigate to WhatsApp > Getting Started in your app dashboard 2. Click "Add phone number" 3. Enter your Israeli number in E.164 format: +972XXXXXXXXX 4. Choose verification method (SMS or voice call) 5. Enter the verification code
Notes for Israeli numbers:
- Mobile numbers:
+9725XXXXXXXX(10 digits after country code) - Landline numbers are supported but mobile is recommended
- The number must not be currently registered on WhatsApp or WhatsApp Business app
4. Generate Access Token
For development: 1. Go to WhatsApp > Getting Started 2. Copy the temporary access token (valid 24 hours)
For production: 1. Go to App Settings > Basic 2. Note your App ID and App Secret 3. Create a System User in Business Settings 4. Generate a permanent token for the System User with whatsapp_business_messaging permission
5. Set Up Webhook
Configure your webhook endpoint to receive incoming messages:
1. Go to WhatsApp > Configuration 2. Enter your webhook URL: https://your-domain.com/webhook 3. Enter your verify token (a secret string you choose) 4. Subscribe to these webhook fields:
messages(incoming messages)message_status(delivery/read receipts)
Webhook requirements:
- Must be HTTPS (SSL certificate required)
- Must respond to GET verification requests
- Must respond with HTTP 200 within 5 seconds
- Must verify the X-Hub-Signature-256 header
6. Create Message Templates (Hebrew)
WhatsApp requires pre-approved templates for initiating conversations. Submit Hebrew templates via Meta Business Suite:
1. Go to WhatsApp > Message Templates 2. Click "Create Template" 3. Select language: Hebrew (he) 4. Choose category: Marketing, Utility, or Authentication
Template Examples for Israeli Businesses
Order Confirmation (Utility):
Template name: order_confirmation_he
Language: he
Category: Utility
Header: None
Body: שלום {{1}}, ההזמנה שלך מספר {{2}} התקבלה בהצלחה. סכום: ₪{{3}}. צפי למשלוח: {{4}}.
Footer: תודה שקנית אצלנו!
Buttons: [Quick Reply: "בדוק סטטוס"]Appointment Reminder (Utility):
Template name: appointment_reminder_he
Language: he
Category: Utility
Body: שלום {{1}}, תזכורת לתור שלך ב-{{2}} בתאריך {{3}} בשעה {{4}}. לאישור, לחץ/י על הכפתור למטה.
Buttons: [Quick Reply: "מאשר/ת"] [Quick Reply: "רוצה לשנות"]Shipping Update (Utility):
Template name: shipping_update_he
Language: he
Category: Utility
Body: הזמנה {{1}} נשלחה! מספר מעקב: {{2}}. צפי הגעה: {{3}}. לעקוב אחר המשלוח:
Buttons: [URL: "מעקב משלוח" -> https://tracking.example.com/{{1}}]Template Review Notes:
- Hebrew templates typically take 1-3 business days to review
- Templates must not contain promotional content in the Utility category
- Variables ({{1}}, {{2}}) must not be used for the entire message body
- Footer is optional and limited to 60 characters
- Maximum 3 buttons per template
API Reference
Sending Messages
Base URL: https://graph.facebook.com/v25.0/{PHONE_NUMBER_ID}/messages
Authentication: Bearer token in Authorization header
Send Text Message
curl -X POST "https://graph.facebook.com/v25.0/${PHONE_NUMBER_ID}/messages" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"messaging_product": "whatsapp",
"to": "972501234567",
"type": "text",
"text": {"body": "שלום! איך אפשר לעזור?"}
}'Send Template Message
curl -X POST "https://graph.facebook.com/v25.0/${PHONE_NUMBER_ID}/messages" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"messaging_product": "whatsapp",
"to": "972501234567",
"type": "template",
"template": {
"name": "order_confirmation_he",
"language": {"code": "he"},
"components": [{
"type": "body",
"parameters": [
{"type": "text", "text": "ישראל ישראלי"},
{"type": "text", "text": "12345"},
{"type": "text", "text": "299.90"},
{"type": "text", "text": "יום רביעי, 15/03"}
]
}]
}
}'Send Interactive Buttons
curl -X POST "https://graph.facebook.com/v25.0/${PHONE_NUMBER_ID}/messages" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"messaging_product": "whatsapp",
"to": "972501234567",
"type": "interactive",
"interactive": {
"type": "button",
"body": {"text": "איך אפשר לעזור?"},
"action": {
"buttons": [
{"type": "reply", "reply": {"id": "order", "title": "בדיקת הזמנה"}},
{"type": "reply", "reply": {"id": "support", "title": "תמיכה"}},
{"type": "reply", "reply": {"id": "hours", "title": "שעות פעילות"}}
]
}
}
}'Send Interactive List
curl -X POST "https://graph.facebook.com/v25.0/${PHONE_NUMBER_ID}/messages" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"messaging_product": "whatsapp",
"to": "972501234567",
"type": "interactive",
"interactive": {
"type": "list",
"body": {"text": "בחר/י נושא:"},
"action": {
"button": "לרשימת האפשרויות",
"sections": [{
"title": "שירותים",
"rows": [
{"id": "pricing", "title": "מחירון", "description": "צפייה במחירים"},
{"id": "branches", "title": "סניפים", "description": "מציאת סניף"}
]
}]
}
}
}'Rate Limits and Pricing
Message Limits
New WhatsApp Business accounts start with limited messaging:
| Tier | Messages per 24h | How to Reach |
|---|---|---|
| Tier 1 | 1,000 | Verified business |
| Tier 2 | 10,000 | After sending 2x Tier 1 limit |
| Tier 3 | 100,000 | After sending 2x Tier 2 limit |
| Tier 4 | Unlimited | After sending 2x Tier 3 limit |
Pricing (Israel)
As of July 1, 2025, WhatsApp moved from conversation-based pricing to per-message pricing. You are now charged per delivered template message, not per 24-hour conversation window. Rates depend on the template category and the recipient's country calling code.
Key points under the current model:
- Template categories are billed: marketing, utility, and authentication (authentication has an
authentication_internationalvariant). - Free messages: all non-template (free-form) messages sent inside an open 24-hour customer service window are free. Utility templates delivered inside an open customer service window are also free. Messages within a free entry-point window are free for 72 hours.
- Service conversations as a billing category no longer exist; what used to be a "service conversation" is now just free-form messaging inside the customer service window.
Per-message rates by country and category change regularly. Do not hardcode rates. Check Meta's official pricing page for current Israel rates: https://developers.facebook.com/documentation/business-messaging/whatsapp/pricing
Best Practices for Israeli WhatsApp Bots
1. Respect Shabbat. Avoid sending marketing messages from Friday sunset to Saturday nightfall. Utility messages (order updates) are generally acceptable.
2. Use Hebrew templates wisely. Hebrew text in templates renders well on all devices. Test on both iOS and Android.
3. Keep button text short. WhatsApp button titles are limited to 20 characters. Hebrew can be more compact than English, so plan accordingly.
4. Handle billing in NIS. When displaying prices, use the ₪ symbol and Israeli number formatting (1,000.00).
5. Support both Hebrew and English. Many Israeli users are bilingual. Offer a language toggle early in the conversation.
6. Comply with Israeli privacy laws. The Israeli Privacy Protection Law (1981) and its regulations apply to WhatsApp communications. Ensure proper consent for marketing messages. Amendment 13 to the law (in force August 2025) tightened consent, notice, and accountability obligations for processing personal data, so review your consent flow and data handling against the updated requirements.
7. Use interactive messages. Israeli users engage more with buttons and lists than free-text input. Use interactive messages wherever possible.
8. Business hours awareness. Israeli business hours are Sunday through Thursday. Configure your bot to acknowledge off-hours messages and set expectations for response times.
Common Issues and Solutions
Phone Number Verification Failed
- Ensure the number is not registered on WhatsApp personal or WhatsApp Business app
- Try voice call verification instead of SMS
- Israeli virtual numbers may not work; use a SIM-based number
Template Rejected
- Ensure Hebrew text is grammatically correct
- Remove any promotional language from Utility templates
- Variables must have sample values during submission
- Resubmit with suggested changes from the rejection reason
Messages Not Delivering
- Check the recipient's phone number format (must be E.164: 972XXXXXXXXX, no leading + in API)
- Verify the recipient has WhatsApp installed
- Check your messaging tier limits
- Ensure the 24-hour conversation window is active for non-template messages
Webhook Not Receiving Messages
- Verify SSL certificate is valid and not self-signed
- Ensure webhook responds with HTTP 200 within 5 seconds
- Check that you've subscribed to the correct webhook fields
- Verify the X-Hub-Signature-256 validation is correct
"""
Telegram Bot Scaffold with Hebrew Support
A starter Telegram bot with:
- Hebrew command menus and inline keyboards
- Conversation state management
- RTL text support
- FAQ system
- Human agent handoff
- Bilingual support (Hebrew/English toggle)
Requirements:
pip install python-telegram-bot>=21.0
Environment Variables:
TELEGRAM_BOT_TOKEN - Bot token from @BotFather
Usage:
export TELEGRAM_BOT_TOKEN=your_bot_token
python telegram-bot-scaffold.py
"""
import logging
import os
from datetime import datetime, timezone
from enum import Enum, auto
from typing import Any
from telegram import (
InlineKeyboardButton,
InlineKeyboardMarkup,
ReplyKeyboardMarkup,
Update,
)
from telegram.ext import (
Application,
CallbackQueryHandler,
CommandHandler,
ContextTypes,
ConversationHandler,
MessageHandler,
filters,
)
# Configuration
BOT_TOKEN = os.environ.get("TELEGRAM_BOT_TOKEN", "")
# Logging
logging.basicConfig(
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
level=logging.INFO,
)
logger = logging.getLogger("telegram-bot")
# =============================================================================
# Conversation States
# =============================================================================
class State(Enum):
MAIN_MENU = auto()
WAITING_ORDER_NUMBER = auto()
WAITING_FEEDBACK = auto()
LANGUAGE_SELECT = auto()
HUMAN_HANDOFF = auto()
# =============================================================================
# Hebrew Text Constants
# =============================================================================
TEXTS = {
"he": {
"welcome": (
"שלום! אני הבוט של [שם העסק].\n\n"
"איך אפשר לעזור לך היום?"
),
"help": (
"הנה מה שאני יודע/ת לעשות:\n\n"
"/start - התחלת שיחה חדשה\n"
"/menu - תפריט ראשי\n"
"/order - בדיקת סטטוס הזמנה\n"
"/faq - שאלות נפוצות\n"
"/language - שינוי שפה\n"
"/help - הצגת עזרה"
),
"main_menu": "בחר/י אחת מהאפשרויות:",
"btn_new_order": "הזמנה חדשה",
"btn_check_status": "בדיקת סטטוס",
"btn_faq": "שאלות נפוצות",
"btn_contact": "דבר/י עם נציג",
"btn_back": "חזרה לתפריט",
"order_ask": "שלח/י לי את מספר ההזמנה:",
"order_invalid": "מספר הזמנה לא תקין. צריך להכיל לפחות 4 ספרות. נסה/י שוב:",
"order_not_found": "לא מצאתי הזמנה עם המספר הזה. בדוק/י ונסה/י שוב.",
"not_understood": "לא הצלחתי להבין. הקלד/י /menu לתפריט.",
"goodbye": "תודה ויום טוב!",
"human_handoff": (
"מעביר/ה אותך לנציג אנושי.\n"
"שעות פעילות: א'-ה' 9:00-17:00\n\n"
"תאר/י בקצרה את הנושא שלך:"
),
"human_ack": "ההודעה שלך נשמרה. נציג יחזור אליך בהקדם.",
"faq_title": "שאלות נפוצות:",
"faq_hours": "שעות פעילות:\nא'-ה': 9:00-17:00\nו': 9:00-13:00\nשבת: סגור",
"faq_returns": "ניתן להחזיר מוצרים עד 14 יום מתאריך הרכישה.\nיש להציג חשבונית.",
"faq_shipping": "אנחנו שולחים לכל הארץ.\nמשלוח רגיל: 5-7 ימי עסקים.\nמשלוח מהיר: 1-2 ימי עסקים.",
"faq_payment": (
"אמצעי תשלום:\n"
"- כרטיס אשראי (ויזה, מאסטרקארד, אמקס)\n"
"- ביט / פייבוקס\n"
"- העברה בנקאית\n"
"- תשלומים (עד 12 ת\"ש ללא ריבית)"
),
"feedback_ask": "נשמח לשמוע מה חשבת! כתוב/י לנו משוב:",
"feedback_thanks": "תודה על המשוב! זה עוזר לנו להשתפר.",
"language_prompt": "בחר/י שפה / Choose language:",
"language_changed": "השפה שונתה לעברית.",
},
"en": {
"welcome": (
"Hello! I'm the [Business Name] bot.\n\n"
"How can I help you today?"
),
"help": (
"Here's what I can do:\n\n"
"/start - Start a new conversation\n"
"/menu - Main menu\n"
"/order - Check order status\n"
"/faq - Frequently asked questions\n"
"/language - Change language\n"
"/help - Show help"
),
"main_menu": "Choose an option:",
"btn_new_order": "New Order",
"btn_check_status": "Check Status",
"btn_faq": "FAQ",
"btn_contact": "Contact Agent",
"btn_back": "Back to Menu",
"order_ask": "Send me your order number:",
"order_invalid": "Invalid order number. Must be at least 4 digits. Try again:",
"order_not_found": "Order not found. Please check and try again.",
"not_understood": "I didn't understand. Type /menu for options.",
"goodbye": "Thank you and have a great day!",
"human_handoff": (
"Transferring you to a human agent.\n"
"Business hours: Sun-Thu 9:00-17:00\n\n"
"Please describe your issue briefly:"
),
"human_ack": "Your message has been saved. An agent will get back to you soon.",
"faq_title": "Frequently Asked Questions:",
"faq_hours": "Business Hours:\nSun-Thu: 9:00-17:00\nFri: 9:00-13:00\nSat: Closed",
"faq_returns": "Products can be returned within 14 days of purchase.\nReceipt required.",
"faq_shipping": "We ship nationwide.\nStandard: 5-7 business days.\nExpress: 1-2 business days.",
"faq_payment": (
"Payment methods:\n"
"- Credit card (Visa, Mastercard, Amex)\n"
"- Bit / PayBox\n"
"- Bank transfer\n"
"- Installments (up to 12, interest-free)"
),
"feedback_ask": "We'd love to hear your feedback! Write us a note:",
"feedback_thanks": "Thank you for your feedback! It helps us improve.",
"language_prompt": "Choose language / בחר/י שפה:",
"language_changed": "Language changed to English.",
},
}
def get_text(context: ContextTypes.DEFAULT_TYPE, key: str) -> str:
"""Get localized text based on user's language preference."""
lang = context.user_data.get("language", "he")
return TEXTS.get(lang, TEXTS["he"]).get(key, key)
# =============================================================================
# Keyboard Builders
# =============================================================================
def build_main_menu_keyboard(lang: str = "he") -> InlineKeyboardMarkup:
"""Build the main menu inline keyboard."""
t = TEXTS.get(lang, TEXTS["he"])
keyboard = [
[
InlineKeyboardButton(t["btn_new_order"], callback_data="new_order"),
InlineKeyboardButton(t["btn_check_status"], callback_data="check_status"),
],
[
InlineKeyboardButton(t["btn_faq"], callback_data="faq"),
InlineKeyboardButton(t["btn_contact"], callback_data="human_agent"),
],
]
return InlineKeyboardMarkup(keyboard)
def build_faq_keyboard(lang: str = "he") -> InlineKeyboardMarkup:
"""Build the FAQ inline keyboard."""
t = TEXTS.get(lang, TEXTS["he"])
if lang == "he":
rows = [
[InlineKeyboardButton("שעות פעילות", callback_data="faq_hours")],
[InlineKeyboardButton("מדיניות החזרות", callback_data="faq_returns")],
[InlineKeyboardButton("משלוחים", callback_data="faq_shipping")],
[InlineKeyboardButton("אמצעי תשלום", callback_data="faq_payment")],
[InlineKeyboardButton(t["btn_back"], callback_data="back_menu")],
]
else:
rows = [
[InlineKeyboardButton("Business Hours", callback_data="faq_hours")],
[InlineKeyboardButton("Return Policy", callback_data="faq_returns")],
[InlineKeyboardButton("Shipping", callback_data="faq_shipping")],
[InlineKeyboardButton("Payment Methods", callback_data="faq_payment")],
[InlineKeyboardButton(t["btn_back"], callback_data="back_menu")],
]
return InlineKeyboardMarkup(rows)
def build_back_keyboard(lang: str = "he") -> InlineKeyboardMarkup:
"""Build a simple back-to-menu keyboard."""
t = TEXTS.get(lang, TEXTS["he"])
return InlineKeyboardMarkup([
[InlineKeyboardButton(t["btn_back"], callback_data="back_menu")]
])
def build_language_keyboard() -> InlineKeyboardMarkup:
"""Build language selection keyboard."""
return InlineKeyboardMarkup([
[
InlineKeyboardButton("עברית", callback_data="lang_he"),
InlineKeyboardButton("English", callback_data="lang_en"),
]
])
# =============================================================================
# Command Handlers
# =============================================================================
async def cmd_start(update: Update, context: ContextTypes.DEFAULT_TYPE):
"""Handle /start command."""
# Default to Hebrew
if "language" not in context.user_data:
context.user_data["language"] = "he"
lang = context.user_data["language"]
await update.message.reply_text(
get_text(context, "welcome"),
reply_markup=build_main_menu_keyboard(lang),
)
async def cmd_help(update: Update, context: ContextTypes.DEFAULT_TYPE):
"""Handle /help command."""
await update.message.reply_text(get_text(context, "help"))
async def cmd_menu(update: Update, context: ContextTypes.DEFAULT_TYPE):
"""Handle /menu command."""
lang = context.user_data.get("language", "he")
await update.message.reply_text(
get_text(context, "main_menu"),
reply_markup=build_main_menu_keyboard(lang),
)
async def cmd_order(update: Update, context: ContextTypes.DEFAULT_TYPE):
"""Handle /order command."""
context.user_data["state"] = State.WAITING_ORDER_NUMBER.value
await update.message.reply_text(get_text(context, "order_ask"))
async def cmd_faq(update: Update, context: ContextTypes.DEFAULT_TYPE):
"""Handle /faq command."""
lang = context.user_data.get("language", "he")
await update.message.reply_text(
get_text(context, "faq_title"),
reply_markup=build_faq_keyboard(lang),
)
async def cmd_language(update: Update, context: ContextTypes.DEFAULT_TYPE):
"""Handle /language command."""
await update.message.reply_text(
TEXTS["he"]["language_prompt"],
reply_markup=build_language_keyboard(),
)
# =============================================================================
# Callback Query Handler (Inline Keyboard Buttons)
# =============================================================================
async def handle_callback(update: Update, context: ContextTypes.DEFAULT_TYPE):
"""Handle inline keyboard button presses."""
query = update.callback_query
await query.answer()
lang = context.user_data.get("language", "he")
data = query.data
# Language selection
if data == "lang_he":
context.user_data["language"] = "he"
await query.edit_message_text(
TEXTS["he"]["language_changed"],
reply_markup=build_main_menu_keyboard("he"),
)
return
if data == "lang_en":
context.user_data["language"] = "en"
await query.edit_message_text(
TEXTS["en"]["language_changed"],
reply_markup=build_main_menu_keyboard("en"),
)
return
# Main menu actions
if data == "back_menu":
await query.edit_message_text(
get_text(context, "main_menu"),
reply_markup=build_main_menu_keyboard(lang),
)
return
if data == "new_order":
# TODO: Implement order creation flow
await query.edit_message_text(
get_text(context, "order_ask") if lang == "he"
else "What would you like to order?",
reply_markup=build_back_keyboard(lang),
)
return
if data == "check_status":
context.user_data["state"] = State.WAITING_ORDER_NUMBER.value
await query.edit_message_text(
get_text(context, "order_ask"),
reply_markup=build_back_keyboard(lang),
)
return
if data == "faq":
await query.edit_message_text(
get_text(context, "faq_title"),
reply_markup=build_faq_keyboard(lang),
)
return
if data == "human_agent":
context.user_data["state"] = State.HUMAN_HANDOFF.value
await query.edit_message_text(get_text(context, "human_handoff"))
return
# FAQ answers
faq_keys = ["faq_hours", "faq_returns", "faq_shipping", "faq_payment"]
if data in faq_keys:
await query.edit_message_text(
get_text(context, data),
reply_markup=build_back_keyboard(lang),
)
return
# =============================================================================
# Text Message Handler
# =============================================================================
async def handle_text(update: Update, context: ContextTypes.DEFAULT_TYPE):
"""Handle free-text messages based on conversation state."""
text = update.message.text.strip()
state = context.user_data.get("state")
lang = context.user_data.get("language", "he")
# Waiting for order number
if state == State.WAITING_ORDER_NUMBER.value:
if text.isdigit() and len(text) >= 4:
# TODO: Replace with actual order lookup
order_info = (
f"הזמנה מספר {text}:\n"
f"סטטוס: בדרך אליך\n"
f"צפי הגעה: מחר, 14:00-18:00"
) if lang == "he" else (
f"Order #{text}:\n"
f"Status: On the way\n"
f"ETA: Tomorrow, 14:00-18:00"
)
await update.message.reply_text(
order_info,
reply_markup=build_back_keyboard(lang),
)
context.user_data["state"] = None
else:
await update.message.reply_text(get_text(context, "order_invalid"))
return
# Human handoff: save message for agent
if state == State.HUMAN_HANDOFF.value:
# TODO: Forward to support system
logger.info(
"Support ticket from %s: %s",
update.effective_user.id,
text,
)
await update.message.reply_text(
get_text(context, "human_ack"),
reply_markup=build_back_keyboard(lang),
)
context.user_data["state"] = None
return
# Waiting for feedback
if state == State.WAITING_FEEDBACK.value:
logger.info("Feedback from %s: %s", update.effective_user.id, text)
await update.message.reply_text(
get_text(context, "feedback_thanks"),
reply_markup=build_back_keyboard(lang),
)
context.user_data["state"] = None
return
# Default: not understood
await update.message.reply_text(get_text(context, "not_understood"))
# =============================================================================
# Error Handler
# =============================================================================
async def error_handler(update: object, context: ContextTypes.DEFAULT_TYPE):
"""Handle errors."""
logger.error("Exception while handling update:", exc_info=context.error)
# =============================================================================
# Main
# =============================================================================
def main():
"""Start the bot."""
if not BOT_TOKEN:
logger.error("TELEGRAM_BOT_TOKEN environment variable not set")
return
# Build application
app = Application.builder().token(BOT_TOKEN).build()
# Command handlers
app.add_handler(CommandHandler("start", cmd_start))
app.add_handler(CommandHandler("help", cmd_help))
app.add_handler(CommandHandler("menu", cmd_menu))
app.add_handler(CommandHandler("order", cmd_order))
app.add_handler(CommandHandler("faq", cmd_faq))
app.add_handler(CommandHandler("language", cmd_language))
# Callback query handler (inline keyboard buttons)
app.add_handler(CallbackQueryHandler(handle_callback))
# Text message handler (must be added last)
app.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, handle_text))
# Error handler
app.add_error_handler(error_handler)
# Start polling
logger.info("Bot started. Polling for updates...")
app.run_polling(allowed_updates=Update.ALL_TYPES)
if __name__ == "__main__":
main()
"""
WhatsApp Cloud API Webhook Handler
A complete webhook handler for the WhatsApp Business Cloud API with:
- Webhook verification (GET endpoint)
- Message signature verification
- Incoming message routing (text, interactive, media)
- Hebrew response templates
- Session management for conversation state
Requirements:
pip install flask requests
Environment Variables:
WHATSAPP_VERIFY_TOKEN - Token for webhook verification
WHATSAPP_APP_SECRET - App secret for signature verification
WHATSAPP_ACCESS_TOKEN - Access token for sending messages
WHATSAPP_PHONE_ID - Phone number ID from Meta dashboard
Usage:
export WHATSAPP_VERIFY_TOKEN=your_verify_token
export WHATSAPP_APP_SECRET=your_app_secret
export WHATSAPP_ACCESS_TOKEN=your_access_token
export WHATSAPP_PHONE_ID=your_phone_number_id
python whatsapp-webhook-handler.py
"""
import hashlib
import hmac
import json
import logging
import os
import sys
from datetime import datetime, timezone
from typing import Any
import requests
from flask import Flask, jsonify, request
# Configuration
VERIFY_TOKEN = os.environ.get("WHATSAPP_VERIFY_TOKEN", "")
APP_SECRET = os.environ.get("WHATSAPP_APP_SECRET", "")
ACCESS_TOKEN = os.environ.get("WHATSAPP_ACCESS_TOKEN", "")
PHONE_NUMBER_ID = os.environ.get("WHATSAPP_PHONE_ID", "")
GRAPH_API_URL = f"https://graph.facebook.com/v25.0/{PHONE_NUMBER_ID}/messages"
# Logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger("whatsapp-bot")
# Flask app
app = Flask(__name__)
# Simple in-memory session store (use Redis in production)
sessions: dict[str, dict[str, Any]] = {}
# =============================================================================
# Hebrew Response Templates
# =============================================================================
RESPONSES = {
"welcome": (
"שלום! ברוכים הבאים.\n\n"
"איך אפשר לעזור לך היום?"
),
"main_menu": "בחר/י אחת מהאפשרויות:",
"not_understood": "לא הצלחתי להבין. אפשר לנסח אחרת?",
"processing": "רגע, בודק/ת...",
"error": "משהו השתבש. נסה/י שוב מאוחר יותר.",
"goodbye": "תודה ויום טוב!",
"human_handoff": (
"מעביר/ה אותך לנציג אנושי.\n"
"שעות פעילות: א'-ה' 9:00-17:00\n"
"נציג יחזור אליך בהקדם."
),
"after_hours": (
"שעות הפעילות שלנו הן א'-ה' 9:00-17:00.\n"
"השאר/י הודעה ונחזור אליך ביום העסקים הבא."
),
"order_ask_number": "שלח/י לי את מספר ההזמנה:",
"order_not_found": "לא מצאתי הזמנה עם המספר הזה. בדוק/י ונסה/י שוב.",
"faq_hours": "שעות פעילות:\nא'-ה': 9:00-17:00\nו': 9:00-13:00\nשבת: סגור",
"faq_returns": "ניתן להחזיר מוצרים עד 14 יום מתאריך הרכישה.\nיש להציג חשבונית.",
"faq_shipping": "אנחנו שולחים לכל הארץ.\nמשלוח רגיל: 5-7 ימי עסקים.\nמשלוח מהיר: 1-2 ימי עסקים.",
"faq_payment": (
"אמצעי תשלום:\n"
"- כרטיס אשראי (ויזה, מאסטרקארד, אמקס)\n"
"- ביט / פייבוקס\n"
"- העברה בנקאית\n"
"- תשלומים (עד 12 ת\"ש ללא ריבית)"
),
}
# =============================================================================
# WhatsApp API Helpers
# =============================================================================
def send_text_message(to: str, text: str) -> dict:
"""Send a text message to a WhatsApp number."""
payload = {
"messaging_product": "whatsapp",
"to": to,
"type": "text",
"text": {"body": text},
}
return _send_message(payload)
def send_interactive_buttons(to: str, body_text: str, buttons: list[dict]) -> dict:
"""Send interactive buttons.
buttons: [{"id": "btn_id", "title": "Button Label"}, ...]
Max 3 buttons.
"""
payload = {
"messaging_product": "whatsapp",
"to": to,
"type": "interactive",
"interactive": {
"type": "button",
"body": {"text": body_text},
"action": {
"buttons": [
{"type": "reply", "reply": {"id": b["id"], "title": b["title"]}}
for b in buttons[:3]
]
},
},
}
return _send_message(payload)
def send_interactive_list(
to: str, body_text: str, button_label: str, sections: list[dict]
) -> dict:
"""Send an interactive list.
sections: [{"title": "Section", "rows": [{"id": "row_id", "title": "...", "description": "..."}]}]
"""
payload = {
"messaging_product": "whatsapp",
"to": to,
"type": "interactive",
"interactive": {
"type": "list",
"body": {"text": body_text},
"action": {"button": button_label, "sections": sections},
},
}
return _send_message(payload)
def mark_as_read(message_id: str) -> dict:
"""Mark a message as read (blue checkmarks)."""
payload = {
"messaging_product": "whatsapp",
"status": "read",
"message_id": message_id,
}
return _send_message(payload)
def _send_message(payload: dict) -> dict:
"""Send a message via the WhatsApp Cloud API."""
headers = {
"Authorization": f"Bearer {ACCESS_TOKEN}",
"Content-Type": "application/json",
}
try:
response = requests.post(GRAPH_API_URL, json=payload, headers=headers, timeout=10)
response.raise_for_status()
return response.json()
except requests.RequestException as e:
logger.error("Failed to send message: %s", e)
return {"error": str(e)}
# =============================================================================
# Session Management
# =============================================================================
def get_session(phone: str) -> dict:
"""Get or create a session for a phone number."""
if phone not in sessions:
sessions[phone] = {
"state": "main_menu",
"created_at": datetime.now(timezone.utc).isoformat(),
"last_activity": datetime.now(timezone.utc).isoformat(),
"fallback_count": 0,
"data": {},
}
sessions[phone]["last_activity"] = datetime.now(timezone.utc).isoformat()
return sessions[phone]
def set_state(phone: str, state: str):
"""Update session state."""
session = get_session(phone)
session["state"] = state
session["fallback_count"] = 0 # Reset on valid state change
# =============================================================================
# Message Handlers
# =============================================================================
def handle_text_message(phone: str, text: str, message_id: str):
"""Handle an incoming text message."""
# Mark as read
mark_as_read(message_id)
session = get_session(phone)
state = session["state"]
text_clean = text.strip()
# Global commands (work from any state)
if text_clean in ("תפריט", "menu", "start", "/start"):
show_main_menu(phone)
return
if text_clean in ("עזרה", "help", "/help"):
send_text_message(phone, "הקלד/י 'תפריט' לאפשרויות, או 'נציג' לשיחה עם אדם.")
return
if text_clean in ("נציג", "אדם", "agent"):
send_text_message(phone, RESPONSES["human_handoff"])
set_state(phone, "human_handoff")
return
# State-specific handling
if state == "main_menu":
show_main_menu(phone)
elif state == "waiting_order_number":
handle_order_lookup(phone, text_clean)
elif state == "human_handoff":
# User is waiting for human, acknowledge
send_text_message(phone, "ההודעה שלך נשמרה. נציג יחזור אליך בהקדם.")
else:
# Fallback
session["fallback_count"] += 1
if session["fallback_count"] >= 3:
send_text_message(
phone,
"נראה שאני מתקשה להבין. הקלד/י 'תפריט' לאפשרויות או 'נציג' לשיחה עם אדם."
)
session["fallback_count"] = 0
else:
send_text_message(phone, RESPONSES["not_understood"])
def handle_button_reply(phone: str, button_id: str, message_id: str):
"""Handle an interactive button reply."""
mark_as_read(message_id)
if button_id == "check_order":
send_text_message(phone, RESPONSES["order_ask_number"])
set_state(phone, "waiting_order_number")
elif button_id == "support":
send_text_message(phone, RESPONSES["human_handoff"])
set_state(phone, "human_handoff")
elif button_id == "hours":
send_text_message(phone, RESPONSES["faq_hours"])
show_main_menu(phone)
elif button_id == "faq":
show_faq_list(phone)
elif button_id == "back_menu":
show_main_menu(phone)
else:
send_text_message(phone, RESPONSES["not_understood"])
def handle_list_reply(phone: str, list_id: str, message_id: str):
"""Handle an interactive list selection."""
mark_as_read(message_id)
faq_responses = {
"faq_hours": RESPONSES["faq_hours"],
"faq_returns": RESPONSES["faq_returns"],
"faq_shipping": RESPONSES["faq_shipping"],
"faq_payment": RESPONSES["faq_payment"],
}
if list_id in faq_responses:
send_text_message(phone, faq_responses[list_id])
# Offer to go back to menu
send_interactive_buttons(
phone,
"צריך עוד משהו?",
[
{"id": "back_menu", "title": "חזרה לתפריט"},
{"id": "support", "title": "דבר/י עם נציג"},
],
)
else:
send_text_message(phone, RESPONSES["not_understood"])
# =============================================================================
# Flow Functions
# =============================================================================
def show_main_menu(phone: str):
"""Show the main menu with interactive buttons."""
set_state(phone, "main_menu")
send_interactive_buttons(
phone,
RESPONSES["welcome"],
[
{"id": "check_order", "title": "בדיקת הזמנה"},
{"id": "faq", "title": "שאלות נפוצות"},
{"id": "support", "title": "דבר/י עם נציג"},
],
)
def show_faq_list(phone: str):
"""Show FAQ options as an interactive list."""
send_interactive_list(
phone,
"בחר/י את הנושא שמעניין אותך:",
"לרשימת הנושאים",
[
{
"title": "שאלות נפוצות",
"rows": [
{"id": "faq_hours", "title": "שעות פעילות", "description": "מתי אנחנו פתוחים"},
{"id": "faq_returns", "title": "מדיניות החזרות", "description": "איך מחזירים מוצר"},
{"id": "faq_shipping", "title": "משלוחים", "description": "אזורי משלוח וזמנים"},
{"id": "faq_payment", "title": "אמצעי תשלום", "description": "דרכי תשלום מקובלות"},
],
}
],
)
def handle_order_lookup(phone: str, order_number: str):
"""Look up an order by number."""
# Validate order number format
if not order_number.isdigit() or len(order_number) < 4:
send_text_message(phone, "מספר הזמנה לא תקין. שלח/י את המספר שוב:")
return
# TODO: Replace with actual order lookup
# order = lookup_order(order_number)
# if order:
# send_text_message(phone, f"הזמנה {order_number}: {order['status']}")
# else:
# send_text_message(phone, RESPONSES["order_not_found"])
# Demo response
send_text_message(
phone,
f"הזמנה מספר {order_number}:\n"
f"סטטוס: בדרך אליך\n"
f"צפי הגעה: מחר, 14:00-18:00"
)
set_state(phone, "main_menu")
# =============================================================================
# Webhook Endpoints
# =============================================================================
@app.route("/webhook", methods=["GET"])
def verify_webhook():
"""Handle webhook verification from Meta."""
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:
logger.info("Webhook verified successfully")
return challenge, 200
logger.warning("Webhook verification failed")
return "Forbidden", 403
@app.route("/webhook", methods=["POST"])
def handle_webhook():
"""Process incoming webhook events from WhatsApp."""
# Verify signature
signature = request.headers.get("X-Hub-Signature-256", "")
body = request.get_data()
if APP_SECRET:
expected = "sha256=" + hmac.new(
APP_SECRET.encode(), body, hashlib.sha256
).hexdigest()
if not hmac.compare_digest(signature, expected):
logger.warning("Invalid webhook signature")
return "Invalid signature", 403
data = request.get_json()
try:
for entry in data.get("entry", []):
for change in entry.get("changes", []):
value = change.get("value", {})
# Handle incoming messages
for message in value.get("messages", []):
phone = message["from"]
msg_id = message["id"]
msg_type = message["type"]
logger.info("Received %s from %s", msg_type, phone)
if msg_type == "text":
handle_text_message(phone, message["text"]["body"], msg_id)
elif msg_type == "interactive":
interactive = message["interactive"]
if "button_reply" in interactive:
handle_button_reply(
phone, interactive["button_reply"]["id"], msg_id
)
elif "list_reply" in interactive:
handle_list_reply(
phone, interactive["list_reply"]["id"], msg_id
)
elif msg_type in ("image", "document", "audio", "video"):
send_text_message(
phone,
"קיבלתי את הקובץ. כרגע אני יודע/ת לטפל רק בהודעות טקסט."
)
# Handle status updates (sent, delivered, read)
for status in value.get("statuses", []):
logger.debug(
"Message %s status: %s",
status.get("id"),
status.get("status"),
)
except Exception:
logger.exception("Error processing webhook")
return jsonify({"status": "error"}), 500
return jsonify({"status": "ok"}), 200
@app.route("/health", methods=["GET"])
def health_check():
"""Health check endpoint."""
return jsonify({"status": "healthy", "service": "whatsapp-bot"}), 200
# =============================================================================
# Main
# =============================================================================
if __name__ == "__main__":
missing = []
if not VERIFY_TOKEN:
missing.append("WHATSAPP_VERIFY_TOKEN")
if not APP_SECRET:
missing.append("WHATSAPP_APP_SECRET")
if not ACCESS_TOKEN:
missing.append("WHATSAPP_ACCESS_TOKEN")
if not PHONE_NUMBER_ID:
missing.append("WHATSAPP_PHONE_ID")
if missing:
logger.error("Missing required environment variables: %s", ", ".join(missing))
logger.error("Set them before starting the server. See docstring for details.")
sys.exit(1)
app.run(host="0.0.0.0", port=8080, debug=True)
Related skills
How it compares
Use instead of generic chatbot skills that ignore RTL layout, Hebrew morphology, and WhatsApp Business API specifics.
FAQ
Who is hebrew-chatbot-builder for?
Developers and small product teams building AI chat experiences for Hebrew-speaking users in Israel, especially on WhatsApp, Telegram, and RTL web widgets.
When should I use hebrew-chatbot-builder?
During Build integrations when you are connecting Hebrew conversational flows to WhatsApp Business API, Telegram, or on-site chat—not for non-Hebrew bots or pure ideation without a channel target.
Is hebrew-chatbot-builder safe to install?
Review the Security Audits panel on this Prism page for install risk and file hash details before wiring production API keys or customer data into chat channels.