July 14, 2018 By David Provan 4 min read

Integration with the Facebook platform

This series of articles was contributed and developed along with Alyssa Small and Brian Adams

This series of posts will detail how IBM iX designed, developed, and delivered the Facebook Messenger Bot available at the The Championships, Wimbledon 2018. The series is broken into three pieces:

  • Part I: This article will focus on the purpose of the Bot, our high-level approach, and how we developed the integration with the Facebook Messenger Platform.

  • Part II: This piece will focus more on the broadcast integration within Facebook and how we persisted user preferences using IBM Cloudant and Compose for Redis.

  • Part III: Finally, we will review our integrations with on-site systems at the All England Club and how we used Multi-Region within IBM Cloud to ensure scale and availability.

Background

During the 2017 Championships, IBM iX created a prototype Messenger application to start exploring delivering content into the platform. The goal was to identify the best way to deliver content on the platform while also starting to understand the elements that would succeed in future deliveries.

After this proof of concept completed, we created a plan to deliver content around four core areas for The 2018 Championships:

  • Scores (live scoring and updates from The Championships)

  • News (latest news and video)

  • Player information

  • Frequently asked questions (FAQs)

We developed a user journey flow, taking advantage of Facebooks Postbacks, quick replies, and other utilities to streamline the user experience.

The idea was to keep interactions simple. We then moved on to design a system capable of allowing us to deliver the desired data points and user features.

High-level architecture

The architecture above is based on the N-tier architecture pattern. This is a pattern familiar to our team, and it allowed us to reuse existing integrations and APIs for the delivery of scoring and content management elements.

We used both Redis Compose and Cloudant for data storage. The Facebook platform (at the time of writing) does not provide a state for a given interaction; each request is stateless. As such, we used Redis to store any useful state information for the interaction. User’s subscriptions were stored and maintained in Cloudant.

The application had three core components. The Facebook Webhook handler handled new messaging payloads from the Messenger Platform. Command identification was used to identify the appropriate services and data components to pass the request off to, and the response was then returned to Facebook through a Facebook Responder component.

This part of the series will focus on our interaction with Facebook instead of the whole flow; those features will be covered in subsequent posts.

Receiving payloads from Facebook

You can find details of the Facebook Webhook for messages on their developer site. IBM iX built a liberty runtime on IBM Cloud to act as our WebHook interaction. You can see the sample framework we built using JAX-RS with Jackson below.

@Path("/webhook")
public class FacebookWebhookProvider extends BaseProvider {

    private static final String VERIFY_TOKEN = "";

    private static final String className = FacebookWebhookProvider.class.getName();

    private static final Logger logger = Logger.getLogger(className);

    @POST
    public Response receiveWebhookFromFacebook(FacebookWebhookEvent facebookWebhookEvent) {

        if ("page".equals(facebookWebhookEvent.getObject())) {
            logger.logp(Level.FINEST, className, "receiveWebhookFromFacebook", "Received webhook event {0}", facebookWebhookEvent);

            BotController botController = new BotController(facebookWebhookEvent);
            botController.actionRequest();

            return Response.ok().build();
        }
        return Response.status(404).build();
    }

    @GET
    public Response verifyWebhook(@QueryParam("hub.mode") String hubMode, @QueryParam("hub.verify_token") String verifyToken, @QueryParam("hub.challenge") String challenge) {
        if (hubMode != null && verifyToken != null) {
            if ("subscribe".equals(hubMode) && VERIFY_TOKEN.equals(verifyToken)) {
                System.out.println("Webhook Verified");
                return Response.ok(challenge).build();
            }
        }
        else {
            return Response.status(403).build();
        }
        return Response.status(403).build();
    }
}

It is important to note here that all our Provider classes extend a common base abstract class that provides common utility functions to all providers. In our implementations, that BaseProvider extends JacksonJsonProvider. This allows for the JAX-RS adaptor to make use of the most recent Jackson annotations, such as @JsonIgnoreProperties(ignoreUnknown = true). If you use the out-of-the-box liberty JSON processor, this tag isn’t adhered to it. You can add the Jackson Base Provider by adding the following to your gradle dependancies.

compile group: 'com.fasterxml.jackson.jaxrs', name: 'jackson-jaxrs-json-provider', version: '2.6.7'

We created the relevant POJO’s for the Provider to parse the data from Facebook into by following the documentation on their developer site for sample payloads.

Responding to Facebook

In future articles in this series, we will demonstrate how we identified and and gathered the appropriate data. In this piece, we will just focus on how we sent payloads into Facebook. First, we looked at the type of data we were going to be sending and matched that to the relevant templates. We then constructed a Responder class as a singleton that any service could invoke to respond to Facebook.

Instead of using Jackson to unmarshall the POJO, we instead used Apache Freemarker, a Java templating library for this activity. We created and compiled templates for each of the response times, and then the Provider would render the standard data objects against the template and send the resulting string into the Facebook Send API.

public enum FacebookResponder {

    INSTANCE;

    Configuration cfg;

    private FacebookResponder() {

        cfg = new Configuration(Configuration.VERSION_2_3_27);                cfg.setClassForTemplateLoading(this.getClass(), "/templates/");

        cfg.setDefaultEncoding("UTF-8");
cfg.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER);
        cfg.setLogTemplateExceptions(false);
        cfg.setWrapUncheckedExceptions(true);

    }

    private final String PAGE_ACCESS_TOKEN = System.getenv("facebookPageAccessToken");

    public void sendResponse(String template, FacebookResponse response) {
        try (CloseableHttpClient client = HttpClientBuilder.create().build()) {

            HttpPost facebookPost = new HttpPost("https://graph.facebook.com/v2.6/me/messages?access_token=" + PAGE_ACCESS_TOKEN);

            facebookPost.setHeader(HttpHeaders.CONTENT_TYPE, "application/json");

            String message = buildResponse(response, template);

            HttpEntity entity = new ByteArrayEntity(message.getBytes("UTF-8"));
            facebookPost.setEntity(entity);

            try (CloseableHttpResponse httpResponse = client.execute(facebookPost)) {
                String stringResponse = EntityUtils.toString(httpResponse.getEntity());
                JSONObject jsonResponse = null;
                jsonResponse = JSONObject.parse(stringResponse);

                if (jsonResponse.containsKey("error")) {
                    logger.severe("payload: " + stringResponse);
                    logger.severe ("Facebook response: " + response.toString());
                }
                else {
                    logger.fine(stringResponse);
                }

            }
            catch (Exception e) {
                e.printStackTrace();
            }

        }

        catch (Exception e) {
            e.printStackTrace();
        }

    }

    public String buildResponse(FacebookResponse response, String template) {
        Template temp;
        StringWriter out = new StringWriter();
        try {
            out = new StringWriter();
            temp = cfg.getTemplate(template);
            temp.process(response, out);
        }
        catch (IOException | TemplateException e) {
            e.printStackTrace();
        }

        return out.toString();
    }

}

Wrap-up

In this article, we have reviewed the base integration with Facebook and our overall approach. In the next part of this series, we will look more closely at how we took the data from Facebook and identified the right service and data to render back to the recipient.

More from

Introducing probable root cause: Enhancing Instana’s Observability

3 min read - We are thrilled to announce an enhancement to Instana® with the introduction of the probable root cause capability, now available in public preview starting from release 277. This capability delivers superior insights, allowing quick identification of the source of a system fault—with little to no investigation time. Probable root cause Working with IBM Research®, we designed an algorithm that use causal AI and differential observability to analyze data modalities such as traces and topology to identify unhealthy entities after an…

Revolutionizing community access to social services: IBM and Microsoft’s collaborative approach

5 min read - In an era when technological advancements and economic growth are often hailed as measures of success, it is essential to pause and reflect on the underlying societal challenges that these advancements often overlook. And to consider how they can be used to genuinely improve the human condition. IBM Consulting® and Microsoft together with government leaders, are answering that call, partnering to develop a platform to bridge the division and enhance the delivery of social services support to communities in need.…

Optimizing data flexibility and performance with hybrid cloud 

3 min read - As the global data storage market is set to more than triple by 2032, businesses face increasing challenges in managing their growing data. This shift to hybrid cloud solutions is transforming data management, enhancing flexibility and boosting performance across organizations.   By focusing on five key aspects of cloud adoption for optimizing data management—from evolving data strategies to ensuring compliance—businesses can create adaptable, high performing data ecosystems that are primed for AI innovation and future growth.  1. The evolution of data…

IBM Newsletters

Get our newsletters and topic updates that deliver the latest thought leadership and insights on emerging trends.
Subscribe now More newsletters