microsoft / botframework-sdk

Bot Framework provides the most comprehensive experience for building conversation applications.
MIT License
7.5k stars 2.44k forks source link

[FB Messenger] Why is the bot not receiving the "messaging_handovers" event from Messenger API? #3764

Closed etbuilder closed 6 years ago

etbuilder commented 6 years ago

Bot Info

Issue Description

I am trying to implement Facebook handover protocol to forward the messages to the page by passing the thread control to the page inbox using app id as in docs (263902037430900), the control is passed successfully, however, the bot do not receive any messaging_handovers event even if the app is already subscribed to it.

As we need to know when the event is received to log it, as well as receive the event when the admin mark the conversation as done and pass the control back to the bot.

Code Example

Follow Facebook guide to enable forward to agent https://developers.facebook.com/docs/messenger-platform/handover-protocol/pass-thread-control#page_inbox

Reproduction Steps

  1. Send call to Facebook to send control to page
  2. Wait for event on MessagesController

Expected Behavior

Receieve an activity with channel data related to webhook

JasonSowers commented 6 years ago

Could you please post your code so we can try to reproduce?

etbuilder commented 6 years ago

I was able to talk to Facebook about it, and to solve the issue, they told me to listen for the standby event and ignore the messages.

What is happening is after passing the control to the page inbox, the bots becomes the secondary controller and it will receive messages of type standby, and by having a proxy before the botframework connector we can ignore these.

JasonSowers commented 6 years ago

Glad you were able to find a solution, good luck!

garypretty commented 6 years ago

@etbuilder any chance you can share some sample code for how you implemented this? I have exactly the same requirement.

etbuilder commented 6 years ago

We enabled standy-by Webhook events on our Facebook app, then in our proxy we just check if the message type is standby we ignore it.

The facebook app webhook is connected to our proxy, in the proxy we check raw messages coming from facebook, and based on type we simply forward them to the botconnecter url .

Code in the proxy

`if (IsStandByEvent(webhookRequestJson)) { return req.CreateResponse(HttpStatusCode.OK); }

private static bool IsStandByEvent(dynamic request) { try { if (request?.entry?[0]?.standby?[0] != null) { return true; } } catch { } return false; }`

garypretty commented 6 years ago

@etbuilder Thanks for that. Exactly what I needed :)

etbuilder commented 6 years ago

You are welcome, you can ping me any time :)

garypretty commented 6 years ago

Hey @etbuilder . Have you got an example of the proxy you built between FB and the Bot Connector? Cannot get mine working.

Thanks

Gary

etbuilder commented 6 years ago

Sure thing

ours is Http trigger azure function

if (req.GetQueryString("hub.mode") == "subscribe" && req.GetQueryString("hub.verify_token") == token) {
                log.Info($"Recieved Validation Request From Facebook with Challenge {Convert.ToInt32(req.GetQueryString("hub.challenge"))}");
                return req.CreateResponse(HttpStatusCode.OK, Convert.ToInt32(req.GetQueryString("hub.challenge")));
            }

jsonContent = await req.Content.ReadAsStringAsync();
webhookRequestJson = JsonConvert.DeserializeObject(jsonContent);

Then we clone the request removing x-liveupgrade and return the response

// forward request to appropriate webhook
forwardRequest = req.Clone(targetWebhook, log);
response = await RouteRequest(forwardRequest, log);

(!response.IsSuccessStatusCode) {
                        log.Error("response was not successful" + response.StatusCode);
 }

return response;
public static HttpRequestMessage Clone(this HttpRequestMessage req, string newUri, TraceWriter log) {
            HttpRequestMessage clone = new HttpRequestMessage(req.Method, newUri);

            if (req.Method != HttpMethod.Get) {
                clone.Content = req.Content;
            }
            clone.Version = req.Version;

            foreach (KeyValuePair<string, object> prop in req.Properties) {
                clone.Properties.Add(prop);
            }

            foreach (KeyValuePair<string, IEnumerable<string>> header in req.Headers) {
                if (header.Key == "X-LiveUpgrade") {
                    continue;
                }

                clone.Headers.TryAddWithoutValidation(header.Key, header.Value);
            }

            //foreach (KeyValuePair<string, IEnumerable<string>> header in clone.Headers)
            //{
            //    log.Info($"header.Key {header.Key}, header.Value {string.Join(",", header.Value.ToArray())}");
            //}

            clone.Headers.Host = new Uri(newUri).Authority;

            return clone;
        }
garypretty commented 6 years ago

@etbuilder great, thanks! and it is in here that you are checking if the message is a standby message and ignoring if necessary?

garypretty commented 6 years ago

@etbuilder Thanks again for the above. I am struggling to get it compiling. Looks like a few methods are missing, like RouteRequest and it doesn't seem to like GetQueryString either. Would you be able to share a more complete sample? Sorry to be a pain, just looking forward to getting this working :)

etbuilder commented 6 years ago
public static string GetQueryString(this HttpRequestMessage request, string key) {
            var queryStrings = request.GetQueryNameValuePairs();
            if (queryStrings == null)
                return null;

            var match = queryStrings.FirstOrDefault(kv => string.Compare(kv.Key, key, true) == 0);
            if (string.IsNullOrEmpty(match.Value))
                return null;

            return match.Value;
        }
public static async Task<HttpResponseMessage> RouteRequest(HttpRequestMessage forwardRequest, TraceWriter log) {
            HttpClient client = new HttpClient();
            HttpResponseMessage response = await client.SendAsync(forwardRequest);

            // log any failure messages in a table for checking later on
            //
            //
            if (!response.IsSuccessStatusCode) {
                //await InsertDeadRequest(response.ToString(), "backend webhook error", log);
                log.Info($"Failed {response.ToString()}");
            }

            return response;
        }

Answering the previous question, the answer is yes.

garypretty commented 6 years ago

@etbuilder Thanks for your help. I just got this working perfectly. Really appreciate the directions on this :) I'll blog about it soon and give you a shout out when I do. You got a twitter handle / blog I can use to reference you?

garypretty commented 6 years ago

@etbuilder Hey. I just ran a quick test and it looks like the standby messages are not sent to the bot directly, even when the proxy is not in place, as long as you have not subscribed to that webhook in Facebook. With this being the case, how come you needed the proxy in the first place?

pascaly commented 6 years ago

Hello @etbuilder I'm working to a chatbot that needs the operator handover. I'm using as primary my chatbot app and as secondary the InBox of facebook. Anyway I see many strange behaves... It seems that if I operate from the facebook business manager to change the handover status from ARCHIVE (that as far as I understood means chatbot) and InBox (that is the operator) or viceversa, this status is automatically changed by facebook without any apparent reason and without sending any web_hook message. Moreover, the pass_thread message seems to have always the same data, app_id of the main one, both if you pass from main to secondary that from secondary to main. This seems to be a bug.

Basically, handover seems totally unstable.

Did you experienced the same?

Thanks

etbuilder commented 6 years ago

Hey,

Once you pass thread control from main to page, are you using the page inbox app id "263902037430900", if the pass is successful you should receive success (I don't think in this case you will receive a web-hook event).

Once you press "Done" on a message from the inbox, you should receive the event, but, the issue is that bot-connector drops it. If you need this event, you need to intercept all calls from the web-hook between Facebook and bot-connector and check if its handover event, forward it directly to your API endpoint.

Further, if you pass thread control to inbox, you will still receive any postback events from bot-connector like button clicks, to resolve this issue, you have to subscribe to standby events, and when you intercept the webhook messages, you can have your logic in which we simply drop them and not forwarding them to the bot-connector.

pascaly commented 6 years ago

Hi @etbuilder, thanks for your reply. In my case, the issue is on facebook it self: even if I disconnect all the webhook and api management for handover, I see that facebook switches from ARCHIVE to INBOX the conversation, automatically. That seems a facebook bug. My question is if someone experienced this behave.

Thanks

etbuilder commented 6 years ago

Have you set your configuration for the page, app as primary and page inbox as secondary?

pascaly commented 6 years ago

Sure. I see the events and previously it was working. suddenly, it started to switch back to INBOX even if I selected ARCHIVE. Once I select ARCHIVE it correctly moves back to ARCHIVED folder, but after the first message is sent to the BOT it goes back to INBOX. This happens also without BotBuilder.

hoangphhut commented 6 years ago

I found that Facebook Messenger support pass thread control from human manager to bot when the conversation was set 'done' by the human. However I don't know the case if a page has several page managers. My experience is that bot receives call back thread control only when ALL page manager set conversation to 'done'. It's not realistic since several page managers but normally only one is active.

Anyone have solution?

bhushanvadgave commented 6 years ago

@etbuilder I am looking for an equivalent code sample for NodeJS/Javascript. Can you help?

ABinfinity commented 6 years ago

plz anyone help with this similar issue https://github.com/Microsoft/BotBuilder/issues/1465

1110770MiguelSilva commented 6 years ago

@garypretty Did you end up doing a blog about this issue? I'm having the same problem, and was seeing if there was a way to solve this without the use of a proxy. I guess in the bot builder there is no way to know if a message comes after the thread has been passed to the inbox? Like in the MessagesController for example.

I've set everything up, the bot app as primary and the inbox as secondary, the bot app's webhook is set to receive standby events, but now I don't know how to know when these standby events happen. So everytime I send the conversation to the inbox, when the user sends a new message it just goes back to the bot.

amitbend commented 6 years ago

@bhushanvadgave @sharmadine I created a gist with an example, I made it quick and dirty for now - but it works https://gist.github.com/amitbend/9c2109603f8913ab46470e3bea3be7c2

1110770MiguelSilva commented 6 years ago

@etbuilder Hello, could you help me build the proxy like you built it? I am trying to build one in this issue: https://github.com/BotBuilderCommunity/botbuilder-community-dotnet/issues/7