I’ve worked in payment systems for a long time, and one common theme is that they make a lot of 3rd party API calls. For example, there are numerous payment networks and banks, fraud and risk checks, onboarding and KYC/KYB (Know Your Customer/Business) services, and more. And then there are the inbound calls as well, such as webhooks and callbacks from integration partners, e-commerce stores, and other outside interactions.
The first step is storing all inbound and outbound API calls. People will often use logs for this, but I think it’s far more valuable to put them in the database instead. This makes them easier to query, easier to aggregate, and easier to join against other data.
You can use one table for both inbound and outbound or separate them depending on preferences, but generally it’s useful to store most of the available information, such as:
URL of the request
Datetime of the request
Request body
Response body
Response status/code (e.g. 200, 500)
Total time spent on the request
Request headers
Response headers
Metadata
For metadata, I like to use a JSON column and add in any metadata that links this request to an entity in the system. For example, it could include user_id, order_id, request_id, etc. As a JSON column, you can include more than one, and even include other complex nested information.
Response headers often include extra debugging information, such as request or trace Ids, version numbers, etc. It’s common when asking for 3rd party support to provide these values so they can go look in their own logging to find your requests.
Rather than try to write code for every API call, it’s often better to hook into the request/response lifecycle in one place and instrument all calls.
The way you do this depends on the language and libraries, but they are generally called interceptors.
Where possible, I like to record the request fields before the outbound call is started (e.g. request body, request headers) and then go back and update the row to store the response fields once the call is completed. There are several advantages over a single write at the end of the request cycle: