Welcome to the Hardenize blog. This is where we will document our journey as we make the Internet a more secure place and have some fun and excitement along the way.
We're continuing to develop our APIs; this month we extended our capabilities with Certificate Transparency (CT) events and webhooks, enabling our customers to receive programmatic real-time notifications of new certificates issued for their domain name space.
Before we started to work on the webhook functionality, we decided to build a strong foundation is the form of events, which are things of interest that happen within an account. After all, we're interested in CT log entries today, but there is a myriad of other things in which we may be interested tomorrow. A generic mechanism for both events and webhooks will make for a much nicer experience in the long term.
An event, as a concept, is rather simple. It contains some metadata to document how it came to be
and when, and to indicate who created it. The really interesting parts of an event are its type and
the embedded data object, which carries the information of interest. For illustration, this is what our
test
event looks like:
{ "id": 10001, "type": "test", "createdOn": "2018-09-03T14:09:59.000Z", "apiVersion": "0.14.0", "source": "hardenize", "data": { "message": "This is a simple test event." } }
This concept of events is not particularly new or innovative. In fact, we've modelled our events after how they are implemented in the Stripe API. We liked their design and decided to adapt it for our needs.
Events are generated only if they are needed. In practice that means that you will first need to selectively activate generation of those event types that you intend to consume. This is all done via our APIs. After that, new events will be deposited into your account as they become available. Event lifetime is limited—you have to collect them within 10 days.
Our webhook implementation builds on top of events. When you create a webhook, you express your interest in a particular set of event types we can generate, and specify the URL at which we should deliver the events. We provide you some tools for the testing, to ensure that you will correctly handle our payloads. After that, whenever a new event is created we schedule one job for every configured webhook that matches the event type. On your end, you receive exactly the same event, plus some additional information that enables you to verify that the webhook came from us.
Should the delivery of a webhook fail, we try again later. In fact, we use an exponential back-off algorithm to try up to 10 times in total within roughly 24 hours. If your webhook remains unavailable for longer, you will miss some events. On the positive side, you can always pull the events from us via the API, provided you do that before the events themselves expire.
With events and webhooks available, we added our first event type ct.entry
. This
event contains roughly the same information found in CT logs. Whenever we see a certificate
with a hostname that matches the domain name space configured for an account, we create
an event similar to the following:
{ "log": "ct.googleapis.com/icarus", "index": 251676662, "timestamp": 1538747492396, "sha256": "7b72bbe8b7faeea24d9bd8ded5f057114a14ad503b33022b54b16603a8b074b7", "precert": false, "subject": "CN=www.example.com", "issuer": "C=US, O=Let's Encrypt, CN=Let's Encrypt Authority X3", "hosts": [ "example.com" ], "chain": [ "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" ], "matchedHosts": [ "example.com" ] }
The complete explanations of the individual fields are available in our API documentation.