From creating an enterprise app to receiving the first AI reply (with four fix-or-it-fails moments)
open.feishu.cn and sign in as your corporate adminApp ID and App Secret (you won't see the secret again)im:message, im:message.group_at_msg, im:message.p2p_msgim.message.receive_v1Get connectivity working before you add AI:
from flask import Flask, request, jsonify
app = Flask(__name__)
@app.route("/webhook", methods=["POST"])
def webhook():
data = request.json
if data.get("type") == "url_verification":
return jsonify({"challenge": data["challenge"]})
event = data.get("header", {}).get("event_type")
if event == "im.message.receive_v1":
msg = data["event"]["message"]
sender = msg["sender"]["sender_id"]["open_id"]
text = msg["content"]["text"]
reply_text = f"You said: {text}"
send_message(sender, reply_text)
return jsonify({"code": 0})
def send_message(receive_id, text):
pass # your existing send impl
if __name__ == "__main__":
app.run(host="0.0.0.0", port=8080)
ngrok http 8080https://xxx.ngrok.io URL into Feishu Event Subscription → Request URL{"code": 0}import requests
def ask_ai(prompt):
resp = requests.post(
"https://your-ai-service/api/chat",
json={"message": prompt, "max_tokens": 1000},
timeout=30
)
return resp.json()["reply"]
# Inside webhook():
reply_text = ask_ai(text)
Once stable, layer on:
URL verification sends {type: "url_verification", challenge: "..."} and expects the response to include challenge verbatim. Forget this branch and you get a generic failure with no clue.
type == "url_verification" before business logic.
tenant_access_token needs App ID + App Secret. Typo in either and you see a generic "call failed" — no hint where.
Feishu distinguishes open_id, user_id, email, and chat_id. Mix them up and Feishu returns 200 with nothing delivered.
open_id, for groups use chat_id. Pick based on the inbound sender_id fields.
Feishu's 100 QPS limit is per tenant. A 20-person group @-ing the bot simultaneously brushes past it.
Recommended next steps in order: