Webhooks
Webhooks allow VIS to send POST
requests to your application whenever user attributes are changed, or users are deleted or merged.
You can enable and configure webhooks for your application in the VIS UI, within your application settings.
Your application must expose an endpoint to receive webhook requests from VIS. This endpoint should trigger any necessary actions in your system.
When a webhook request fails (e.g., the application was unavailable or returned a non-successfull HTTP status code), VIS will retry a few times the request.
After enabling a webhook, you can test it by updating your username on the VIS Credentials page and verifying that the change is reflected in your application's database.
Checking webhook body integrity
For security, each webhook request includes a HMAC-SHA256 signature, built from a secret shared between VIS and your application. Use it to verify that the request body hasn’t been tampered with. Each request also includes a timestamp to prevent replay attacks.
The CALM application verifies these elements like so:
def verify_webhook
return unless req_timestamp_ok?
hmac_header = request.headers["X-Authorization-Content-SHA256"]
return if ActiveSupport::SecurityUtils.secure_compare(calculated_hmac(params[:payload].to_s), hmac_header)
render json: { error: "invalid HMAC signature" }, status: :forbidden
end
private def calculated_hmac(data_str)
digest = OpenSSL::Digest.new("sha256")
hmac_digest = OpenSSL::HMAC.digest(digest, Rails.application.config.vis["webhook_secret"], data_str)
Base64.encode64(hmac_digest).strip
end
private def req_timestamp_ok?
ts = ::JSON.parse(params["payload"])["timestamp_utc"]
return false if ts.blank?
return true if Time.now.utc - Time.parse(ts).utc < 60.seconds
render json: { error: "old timestamp" }, status: :forbidden
false
end
Return a 403 Forbidden status if any of these checks fail. This helps with consistent debugging.
Webhook Types
VIS supports three types of webhooks that notify your application about different user-related events.
You can subscribe to specific webhook events or use "*"
to subscribe to all events.
Configure webhook subscriptions in your application settings within the VIS UI.
User Attribute Updates
Triggered when user attributes are changed in the VIS database, either by the user themselves (e.g., updating email in the VIS UI) or by another application connected to VIS. This is also triggered when a new user record is created.
Payload structure:
{
"event": "events.user_modification",
"payload": {
"id": "user-uuid-here",
"changes": {
"email": ["[email protected]", "[email protected]"],
"given_name": ["John", "Jonathan"],
"family_name": ["Doe", "Smith"]
},
"timestamp_utc": "2023-12-01T10:00:00.000Z"
}
}
The changes
object contains only the attributes that were modified, where each key maps to an array
with the old value (first element) and new value (second element). Possible attributes are: email, old_student, birthdate, given_name, family_name, non_latin_given_name, non_latin_family_name, primary_country, male, username, is_at, at_code, is_cct, mobile, office365_email, invited_to_url, student_account_app.
When the updates were triggered through the API by an application, no webhook event is sent to this application.
User Deletions
Triggered when a VIS user record is permanently deleted (e.g., on a privacy request or when calling the Delete User API endpoint).
Payload structure:
{
"event": "events.user_deletion",
"payload": {
"id": "user-uuid-here",
"timestamp_utc": "2023-12-01T10:00:00.000Z"
}
}
User Merges
Triggered when a user merges multiple VIS accounts into a single account. This happens when VIS detects potential duplicate accounts and the user confirms the merge through an email challenge.
Payload structure:
{
"event": "events.merge_users",
"payload": {
"kept_id": "kept-user-uuid-here",
"merged_id": "merged-user-uuid-here",
"kept_attrs": {
"id": "kept-user-uuid-here",
"email": "[email protected]",
"given_name": "John",
"family_name": "Doe",
"birthdate": "1990-01-01",
"primary_country": "US",
"username": "johndoe",
"male": true,
"old_student": false,
"is_at": false,
"at_code": null,
"is_cct": false,
"mobile": "+1234567890",
"non_latin_given_name": null,
"non_latin_family_name": null,
"office365_email": null,
"invited_to_url": null,
"student_account_app": null
},
"timestamp_utc": "2023-12-01T10:00:00.000Z"
}
}
When a merge occurs, the kept_id
is the UUID of the account that remains active,
while merged_id
is the UUID of the account that was merged and is now inactive.
The kept_attrs
object contains all the current attributes of the kept user account.