Last week I had a frustrating debugging session with an integration partner. They provide financial services we’re integrating into our website. They provide these services via a JSON web service endpoint.

The Project

I have architected a server-to-server solution that enables our server to post and retrieve data to / from their service. This is secured using TLS and JWT. While some integration features are implemented using a client-to-server architecture (and a separate JWT because it’s revealed to the client), the majority of integration features are implemented using a server-to-server architecture with a private JWT. Server-to-Server. Remember that- it’s important to my story.

I have architected a server-to-server solution.

The Problem With Documentation

Any experienced developer will tell you the problem with documentation is it’s often wrong.

When it came time for me to implement a feature that involved uploading files to our integration partner’s service, I found their documentation lacking. Their documentation included an example HTTP request and response for a file upload. It indicated to place file metadata (a descriptive name and a filename) as JSON in the HTTP Post body. Problem was, it made no mention of where to place the file contents. Nor did it mention how to encode the file contents: for example, as unencoded (raw bytes), Base64-encoded (with or without MIME), or any among numerous binary-to-text encoding schemes.

Keep in mind, this is a JSON web service- which is a text-based protocol- so knowing how to encode binary data is essential. Our integration partner’s documentation did not address this issue.

It did, however, provide coding examples in many popular programing languages- C#, Go, Java, JavaScript, Python, Ruby, Swift, VB, etc. Unfortunately, all of these examples were incomplete- I should say, just plain wrong. Most example code did not demonstrate where to place the actual file contents. It merely illustrated how to format, and where to place, the file metadata. Even worse, some example code posted nothing to the endpoint- no metadata nor file contents. Ugh.

I reached out to our integration partner’s support team for clarification on how to post a file to their web service. I asked for 1) details of what to pass in HTTP headers and the body, what if anything to pass in URL querystring parameters, and how to encode binary file contents. I also asked for 2) a HTTP trace of a successful file upload along with (provided separately) the file and its metadata values. I was met with blank stares. Well, I don’t know for sure because this was a Slack conversation, but I sensed in their non-answers confusion over what I was asking. With some prodding, our integration partner partially answered question 1: They said to “Do a multipart/form-data post.” OK, that gave me some direction.

A Vague Suggestion

“Do a multipart/form-data post.”

I wrote code that leveraged Microsoft’s HttpClient class to send a multipart/form-data HTTP request. The integration partner’s server responded with confusing messages about “enabling JavaScript”, “completing a CAPTCHA request”, and “you have been blocked by a security service that protects against online attacks.” The first two responses suggest solutions for interactive client-side problems- not relevant in the server-to-server architecture I had built. The third response suggests their firewall blocked the HTTP request, though to minimize security information disclosure does not indicate why. I forwarded a HTTP trace file of my attempts to our integration partner, requested they indicate what is incorrect about the HTTP requests- or at least clarify the required format of the HTTP Post, but received no helpful details.

Earlier, I had asked for a HTTP trace of a successful file upload to hedge against this exact situation- receiving no details about the required format. To mitigate the risk (to our schedule) of receiving vague or incomplete answers to my first question, I wanted a HTTP trace file. If our integration partner provided me with a trace file and the source content, I could reverse engineer the required format of the HTTP Post. While waiting for a trace file, I moved onto building other features. At one point the support team sent example JavaScript code (different from the example JavaScript published in their documentation). I ignored it because I required a server-to-server solution (not client-to-server via web browser JS), and went back to building other features.

Investigation, Reverse Engineering, and a Solution

After a day of no progress on the file upload issue- no success with variations of my server-to-server code and no details provided by our integration partner- I decided to try the example JavaScript code they sent. I downloaded the required libraries, modified the script’s security token and other details to conform with my configuration, and launched Fiddler. The JavaScript code worked. It successfully uploaded a file. I reviewed the raw bytes of the HTTP request and response captured by Fiddler.

Request
=======

POST https://api.integrationpartner.com/guid/registration/file HTTP/1.1
Authorization: Bearer...
Accept: application/json
Content-Type: multipart/form-data; boundary=----B2AF5407-258A-4ED1-88C3-33BD1CB457D8----
Host: api.integrationpartner.com
Content-Length: 408146

------B2AF5407-258A-4ED1-88C3-33BD1CB457D8----
Content-Type: text/plain; charset=utf-8
Content-Disposition: form-data; name="name"

License Photo
------B2AF5407-258A-4ED1-88C3-33BD1CB457D8----
Content-Disposition: form-data; name="file"; filename="Photo.png"
Content-Type: image/png

PNG file contents...


Response
========

HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 4863
Connection: keep-alive
Cache-Control: no-cache, private

{...
    "files": [{
        "id": "3bc65163...",
        "name": "License Photo",
        "path": "onboarding\/3bc65163...png",
        "public_url": "https:\/\/onboarding.integrationpartner.com\/...3bc65163...png",
        "meta": {
            "filesize_bytes": 407779,
            "filesize": "4.08 kB",
            "extension": "png",
            "size": {
                "width": 510,
                "height": 503
            },
            "mime": "image\/png"
        },
        "created_at": "2020-05-31 14:10:00",
        "updated_at": "2020-05-31 14:10:00",
        "deleted_at": null}, ...
    ]
}

I spotted two key differences between the failed HTTP Post produced by my server-to-server code and the successful HTTP Post produced by this JavaScript code.

  1. The metadata is formatted as semicolon-delimited plain-text in the Content-Disposition header. Not as JSON.
  2. The boundary that separates parts (part 1 is metadata, part 2 is the file contents) is specified in the Content-Type header after a semicolon delimeter.

This was all I needed to correct my code. After making these adjustments, my server-to-server code successfully uploaded a file.

Integration Strategies Past and Present

I admit I should have recognized the confusion present in our integration partner’s support team, and because of this, I should have more expeditiously tried everything they sent me (the example JavaScript code) even when it did not meet my requirements (server-to-server architecture). However, this episode reveals a serious failure of our integration partner: When you are in the business of providing Internet services you had better understand the Internet protocols upon which your services are built. It’s helpful to provide example code to your integration partners that demonstrates how to invoke your services. However, example code is secondary to understanding and documenting what’s sent “on the wire.”

The web protocol is the integration.

The software development industry has moved on from older cross-company integration techniques that involved distributing code via SDKs, DLLs, JARs, etc. Now we integrate via web services so each company can choose the operating systems, programming languages, runtimes, and software frameworks that best meet their needs and best align with their staff’s expertise. As a consequence of this shift in strategy, we software developers must recognize the point of integration no longer resides in binary code distributed to partners. The point of integration is where client messages meet server endpoints.

The point of integration no longer resides in binary code distributed to partners.

In this modern world of SOA architectures, successful integrations are dependent on a thorough understanding of the underlying Internet protocols. In other words, the web protocol is the integration.

Leave a Reply

Your email address will not be published. Required fields are marked *