Skip to content

Instantly share code, notes, and snippets.

@blaisep
Last active March 4, 2020 03:32
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save blaisep/19effdbac4d82489df97aba333d82764 to your computer and use it in GitHub Desktop.
Save blaisep/19effdbac4d82489df97aba333d82764 to your computer and use it in GitHub Desktop.
Facebook Workplace Admin Event Webhook integration

Process Facebook Workplace Admin Events using webhooks

Facebook Workplace charges extra to log admin events. Facebook will publish Admin events for free via webhooks. You can "subscribe" they don't queue and they don't retransmit. To subscribe, you'll need to register a public URL with a recognized TLS certificate. Finally, you'll need a handler at that URL to participate in a rudimentary verification handshake before Facebook will register a webhook destination.

Considering these requirements, the solution has to:

  • set up an externally accessible server
  • get a domain name and a public SSL cert
  • reverse engineering the handshake (because FB doesn't document what fields are exchanged)
  • write a process that could accept the handshake
  • write incoming events to local storage
  • stick it behind a load balancer
  • handle concurrent sessions
  • install a sumo collector and point it to the filesystem.

Facebook Workplace Reference docs will steer you wrong in these areas:

Token verification request, for a single topic: If you only select one topic in the UI, then the callback URL receives this from Facebook: GET http://tools.dev.travellogic.info/jumboinc?hub.mode=subscribe&hub.challenge=202992368&hub.verify_token=aaabbbcccdddeee111222333

in JSON, the payload is spelled:

{"hub.challenge": ["202992368"],
 "hub.mode": ["subscribe"],
 "hub.verify_token": ["aaabbbcccdddeee111222333"]}

Note that it includes the 'hub.mode' even though it's not a command nor it does hub.mode appear on the GUI.

Token verification request, for multiple multiple topics:

If you select multiple topics in the UI, then the callback URL receives a separate request for each topic:

GET http://tools.dev.travellogic.info/jumboinc?hub.mode=subscribe&hub.challenge=202992368&hub.verify_token=aaabbbcccdddeee111222333 
GET http://tools.dev.travellogic.info/jumboinc?hub.mode=subscribe&hub.challenge=1872868000&hub.verify_token=aaabbbcccdddeee111222333
GET http://tools.dev.travellogic.info/jumboinc?hub.mode=subscribe&hub.challenge=828657682&hub.verify_token=aaabbbcccdddeee111222333
GET http://tools.dev.travellogic.info/jumboinc?hub.mode=subscribe&hub.challenge=1616741489&hub.verify_token=aaabbbcccdddeee111222333

So we need to respond to each request with the corresponding value of hub.challenge.

Sanic to the rescue

In this case, I chose Sanic because I might need to handle lots of small transactions and I didn't want to let the HTTPS handling get in the way.

from sanic import request, response
from sanic.response import json
from loguru import logger

"""Write the admin events to a local file so tht it looks like a normal log."""
logger.add("jumboinc.log",
           format="{time} {level} {message}",
           filter="project",
           level="INFO",
           rotation="500 MB",
           enqueue=True)

def setup_routes(app):
    @app.route("/")
    async def test(request):
        return json({
            "hello": "world",
            })

"""Handle the incoming request, and return the verification token if necessary."""
    @app.route("/jumboinc", methods=["GET"])
    async def verify(request):
        logger.info("received '{}' ", request.args)
        if not request.args.get("hub.verify_token") == app.config.VERIFY_TOKEN:
            return response.text("Verification token mismatch", status=403)
        return response.text(request.args["hub.challenge"][0], status=250)

Hosting this process on a public server

If you have EC2 resources available, you could do as I did:

  • created a small server,
  • registered a domain and hostname
  • requested certificate
  • created a load balancer (more for ddos mitigation in this case)
  • associated LB -> target group -> TLS cert -> http server

Using a hosted service

There are services such as ngroks which will provide similar functionality with less hassle. The rate limiting and the cost may be a problem (they were in my case.)

Hosting this process on a private server

Because Facebook expects to connect to a public server (with a cert signed by a well known CA), hosting this internally would mean either:

  • punchng a hole in my firewall
  • creating a reverse tunnel to a public end point. (eg. Inlets)

I didn't know about Inlets at the time. That part will come in the next episode.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment