User Account

Back to docs index

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.