We are currently in the process of developing support for webhooks to allow near real time update of user information in third party systems. Currently our implementation is limited to a small set of data we can send webhooks for, and only a single implementation of transfer format. Please contact us with ideas for future data and transfer formats.
We define webhooks as a REST call to a third party URL with updated userdata triggered by something in our systems. We're using Kafka internally for triggering these messages, so when a user changes privacy preferences, we put the updated preferences on a Kafka topic. If a webhook is registered for this topic, we make a REST call to the registered URL containing the updated user preferences.
We handle temporary failures like 500 responses, timeouts etc. by retrying messages. Failures indicated by 4xx responses are put on a DLQ so they don't stop the entire queue.
We will also trigger webhooks on a regular basis even when nothing has changed. This is to ensure that stale data caued by a missed or failed update is eventually corrected.
user.privacy_preferences.state
This webhook is triggered when a user changes privacy preferences on Min aID. The content is the same as the privacy_preferences
claim in the OpenID token and in the UserV3 public API.
This webhook is (or at least eventually will be) triggered when a user's access changes for whatever reason. The content is the same as the access
claim in the OpenID token and in the UserV3 public API.
This webhook will be triggered when a user attribute like name, phone, email is changed. The content is the same as the top level claims in the OpenID token and in the UserV3 public API.
This webhook is triggered when a user creates or updates a group, for example by joining or leaving. The content is a list of uuids for members, but the format has not been decided yet.
Our goal here is that it should be easy for third party to receive webhook calls, and that it should be secure (ie. some man in between should not be able to provide false webhook calls to update state on behalf of aID).
We have currently only implemented one method, but we are open to consider other methods if there is a need and strong arguments that they fill the above criteria.
This transfer method is based on a simple HTTP POST message containing a signed JWT token. The token should be validated by using the same public key used by our OpenID Connect implementation, which can be found here: https://www.aid.no/oauth/discovery/keys . The format of the token is the same as in our OIDC-JWTs, except that only the claims relevant to the specific webhook will be present. The Content-type HTTP Header in the request is set to application/jwt in accordance with https://www.rfc-editor.org/rfc/rfc7519#section-10.3 . The advantage of this method is that there is no need to set up API keys, passwords, whitelists etc. All the receiver needs to do is to verify the JWT in the same way it's verified during OpenID Connect.
In other words:
- If the JWT can’t be verified by the public key, try to renew the public key and reverify the JWT. Ignore the JWT if verification still fails. (This is to support new keys if we have to renew it in the future).
- If the
exp
claim is before now, ignore it. (This should normally be handled by JWT validation) - If the
iat
claim is too old, ignore it. (More than five minutes should be considered ancient). - If the
aud
is not you, ignore it.
Here is an example of data posted in such a webhook:
eyJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJodHRwczovL3d3dy5haWQubm8vIiwic3ViIjoiY2Q0ZTQ1N2EtNTczZC00MjExLWFjMWItOGVhYmE2ZmM4NjI0IiwiaWF0IjoxNjY1MTQyNjA3LCJhdWQiOiJodHRwOi8vc2VydmljZXMtYWlkLmFwaS5uby9hcGkvYnVtYmxlYmVlL3YxL3ByaXZhY3lfcHJlZmVyZW5jZXMiLCJwcml2YWN5X3ByZWZlcmVuY2VzIjp7ImFsbG93X3Jlc2VhcmNoX3VzYWdlIjpmYWxzZSwiY29ubmVjdF93aXRoX3N1cnZleXMiOmZhbHNlLCJwZXJzb25hbGl6ZWRfYWRzIjpmYWxzZSwicGVyc29uYWxpemVkX2NvbnRlbnQiOnRydWV9fQ.XLVfpGEdDfOF7A_15lKgpf_HiQvJGn7H7qvDuPf7O4gKnjTpA9d6mo2Yr65ZpzAYgkPnMZtrknHG4WBP3m6RTHSGqECEtgQsqlzGHKDIJXSQCG_7WYzVe4ZWsOKbriqvipzm0EJRT9e8mK6UfCT8eFf-rSO4KSJyHqxZYwb-egvElbGEvZPqAFPljwwXJoZbaGgGVDHGfLyB_G3OhTy1Z1FueIlGSHIkuPQn-rEi0upjXiCU-SIrmdimcWu4_tCa0qhckLarqY_5qnLE_5pkh-l9d3K93NKHcHif5QG3nPncIHQYemWjb3S-5n6jtSNoae_nzKrFBahTQF6y_cEURg
This looks like garbage, but it can be decoded on jwt.io into this: {
"iss": "https://www.aid.no/",
"sub": "cd4e457a-573d-4211-ac1b-8eaba6fc8624",
"iat": 1665142607,
"aud": "http://services-aid.api.no/api/bumblebee/v1/privacy_preferences",
"privacy_preferences": {
"allow_research_usage": false,
"connect_with_surveys": false,
"personalized_ads": false,
"personalized_content": true
}
}
So here we can see that this is a user.privacy_preferences.state
, it's for a user with uuid
cd4e457a-573d-4211-ac1b-8eaba6fc8624
(which is an aID test user). It's made for a system we use for testing reception of our webhooks, called Bumblebee. The interesting part here is the privacy_preferences
-claim, which contains new privacy preferences for our user, stating that only personalized content should be allowed.
On jwt.io , the JWT can also be verified by using this key (which is our public key as of 7th of October 2022): {"kty":"RSA","kid":"cxzSmMQFSud_fVId1vVDUpXR1CxrWFeeISmH5ghVQIE","e":"AQAB","n":"z5DUia4JkiryUMGrjSxPyiY1gpSIXxcA9nl2KC4jstGnVcmlxlo0MFqzgLrPWkn9MJc29cyZAI5c4OqAcPbycjNxbz8Ev5zFkzKGSaMhVHuUMtUdsdq687k7ykxhgm2pplXKaPswZqkCqtFrBtmLnbemwXXjCDY1x9mbpY7pnIUaAVPZkDMyp31QZgrvRS_XBU_GMne3mDvvsFd2ZXg4eqQiggy5vg8wg2jYIroiVAEXf5Fhswj0mETDeTGOr4IZObzdGcQcpW6e6EnnPoJaiNYhGqIysHc5M9ewE9wJH5T0_DxjXbuf0gN62OiUXCkpPpd9-PkoEsKDXPS8GFd6Aw","use":"sig","alg":"RS256"}
Example command line for testing receiver of webhooks:
curl -i -X POST -H 'Content-type: application/jwt' --data eyJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJodHRwczovL3d3dy5haWQubm8vIiwic3ViIjoiY2Q0ZTQ1N2EtNTczZC00MjExLWFjMWItOGVhYmE2ZmM4NjI0IiwiaWF0IjoxNjY1MTQyNjA3LCJhdWQiOiJodHRwOi8vc2VydmljZXMtYWlkLmFwaS5uby9hcGkvYnVtYmxlYmVlL3YxL3ByaXZhY3lfcHJlZmVyZW5jZXMiLCJwcml2YWN5X3ByZWZlcmVuY2VzIjp7ImFsbG93X3Jlc2VhcmNoX3VzYWdlIjpmYWxzZSwiY29ubmVjdF93aXRoX3N1cnZleXMiOmZhbHNlLCJwZXJzb25hbGl6ZWRfYWRzIjpmYWxzZSwicGVyc29uYWxpemVkX2NvbnRlbnQiOnRydWV9fQ.XLVfpGEdDfOF7A_15lKgpf_HiQvJGn7H7qvDuPf7O4gKnjTpA9d6mo2Yr65ZpzAYgkPnMZtrknHG4WBP3m6RTHSGqECEtgQsqlzGHKDIJXSQCG_7WYzVe4ZWsOKbriqvipzm0EJRT9e8mK6UfCT8eFf-rSO4KSJyHqxZYwb-egvElbGEvZPqAFPljwwXJoZbaGgGVDHGfLyB_G3OhTy1Z1FueIlGSHIkuPQn-rEi0upjXiCU-SIrmdimcWu4_tCa0qhckLarqY_5qnLE_5pkh-l9d3K93NKHcHif5QG3nPncIHQYemWjb3S-5n6jtSNoae_nzKrFBahTQF6y_cEURg http://my.thirdparty.domain/api/webhooks/privacy_preferences