Tracking User Consent to legal terms

A mailer, member database, and so much more, for digital activism.

Tracking User Consent to legal terms

Background

In order for a user to make use of an organisations platform, and in order for an organisation to store and process a users details, the user must consent to legal terms and conditions. These terms will differ by country and organisation, but as well as the terms themselves, the method of obtaining consent will be different. This is due to different laws and requirements around the world.

For example, in some countries and jurisdictions it may be perfectly legal for an organisation to simply display text saying “by submitting your details, you consent to our terms of service”. In others, a pre-checked tickbox saying “I agree to the terms of service” may be the minimum required, and in others still it would not be legal to pre-tick such a checkbox.

Within Identity there are four levels of consent:

There is also a concept of ‘No Change’, which usually means no consent was obtained on the basis that the user already consented in the past.

Examples of the different levels of consent:

Data Model for Consents

Member consents data model diagram

A ConsentText object, stored in the consent_texts table, represents an immutable legal text which a user may consent to.

The fields are:

A MemberActionConsent object, stored in the member_action_consents table, represents all ConsentTexts a user agreed to when they took an action. The level of consent obtained will often be ‘No Change’ because in most countries / jurisdictions there will be no need to force a user to re-state their consent every time they take action, although it’s possible this could be a requirement in future.

The fields are:

A member’s current consent is now determined by taking the latest ‘no_change’ consent for each consent text from the member_action_consents table.

Usage in other apps

Identity stores all data related to users, but will not actually be able to capture consent for a user. That is left to external applications, most commonly this will be Speakout or ControlShift (or both).

These apps may choose not to re-obtain consent from a user every time they take action, assuming the user has already consented to the required terms in the past.

The following diagrams show how Speakout will operate to show only relevant consents to users. The exact logic which Speakout or other apps use to determine whether to show a consent or not is beyond the scope of this documentation, but it is assumed that consent for the exact same ConsentText will not be obtained multiple times unless a higher level of consent is now required (eg. previously implicit consent was allowed, but now explicit_opt_in consent is required).

What does the API look like?

See the Identity API docs for more background on using the Member Details and Actions APIs.

Finding out what a user has already consented to

The existing API to find out a members data is used (see app/controllers/api/members.rb). This is called by executing a POST on /api/member/details, passing in the guid or email of a user. If the current consents for a user are also required, an additional parameter, load_current_consents must also be passed in, and the method will add to the hash object a list of the current consents in the following format:

{
  "existing_fields...": "...",

  "consents": [
    {
      "public_id": "terms_of_service_1.0",
      "consent_level": "explicit_opt_in",
      "consent_created_at": "2016-12-30 23:30:13 +0000"
    },
    {
      "public_id": "privacy_policy_2.6",
      "consent_level": "implicit",
      "consent_created_at": "2015-01-01 10:10:10 +0000"
    }
  ]
}

The existing API to send actions from external systems to Identity remains in use. This now optionally accepts a consents field in the json payload, which should contain a list of all consents required in order to take the given action. Note that even if a consent option is not displayed to the user because they have opted in previously, if the action requires consent,it should still be listed in the API with a ‘no_change’ consent_level. The format is as follows:

{
  "existing_fields...": "...",

  "consents": [
    {
      "public_id": "terms_of_service_1.0",
      "consent_level": "no_change"
    },
    {
      "public_id": "privacy_policy_2.0",
      "consent_level": "explicit_opt_in",
      "consent_method": "dropdown",
      "consent_method_option": "Yes, I accept"
    },
    {
      "public_id": "donations_policy_1.6",
      "consent_level": "none_given",
      "consent_method": "checkbox"
    }
  ]
}

Setting up Consents in Identity

Once the migrations to add the new database tables is done, you simply need to create the appropriate ConsentText instances in the database. There is currently no way to create a new ConsentText (ie. some text that people will be consenting to) other than through the backend. If another system such as Speakout or ControlShift sends Identity a consent with a public_id which Identity doesn’t recognise, it will throw an error.

Creating a new ConsentText can be done in the Padrino/Ruby console as follows:

ConsentText.create!(
  public_id: 'privacy_policy_v1',
  consent_short_text: 'I consent to the <a href="https://{ your website }/privacy_policy_v1" target="_blank">privacy policy</a>',
  full_legal_text_link: 'https://{ your website }/privacy_policy_v1'
)

Once you have created a ConsentText in Identity, other systems such as Speakout & ControlShift can send the public_id of this consent to Identity. This will involve creating the ConsentText and any other required configuration in the other systems.

IMPORTANT NOTE: The consent_short_text and full_legal_text_link for a public_id must be identical in all systems (Identity, Speakout, ControlShift, etc)! How this is managed and guaranteed is beyond the scope of this documentation.

At this point a consent has been created in Identity, but the same consent must be configured in other systems such as Speakout, Controlshift, etc in order for anything useful to happen. Just having a ConsentText setup in Identity and other linked systems will simply record whether the user opted in or out of the consent text - it won’t actually do anyting in response to a user opting in or out! So additionally, if you want something to happen when a user opts in or out (such as subscribe them to email communications for example) you also need to setup one or more PostConsentMethod entries. See the Updating Consent Texts documentation for more detail on doing this, as well as setting up consents in different linked systems.

Configuration in Identity

Identity may store all consents sent by external applications whenever an action is taken by a user (including ‘no_change’ consents). This is turned off by default. It can be enabled by overriding the value of Settings.consent.record_no_change_consents for your org.

Member data export

Admins can request all data related to a single member, for example in response to a request from that member under EU GDPR regulations. On a member page /members/:id click the Export Data button and you can select between mailing a (password protected) archive to either the email of the logged-in admin or the email of the member. Export emails will be sent using the email address defined in setting.yml...transactional_member_emails There are two environment variables to be set along with this feature:

Configuration / Setup of ‘additional consents’ in CSL.

CSL can offer members additional custom consent questions outside of privacy and contact consents, e.g. asking for peoples age bracket to support ‘Age Appropriate Design’ in the UK.

If you want to use additional custom questions on your CSL instance you will need to:

  1. Setup a ConsentText which matches the question text in ID
  2. Setup any PostConsentMethods in ID as necessary (ie. if you want a person responding in a particular way to the question to have some impact other than just being recorded - for example, you want to subscribe or unsubscribe someone from a particular subscription)
  3. Setup a ControlshiftConsent, using the desired question slug as the controlshift_consent_external_id
  4. Setup a ControlshiftConsentMapping, which maps from the ControlshiftConsent to the ConsentText and uses the appropriate consent_level and consent_method_option depending upon whether the user opted in or out
  5. Let CSL Support know that you would like an additional custom question added when people sign a petition - tell them (a) the question text, (b) the question slug which will appear as the key in the additional_fields hash and should match what was used in the ControlshiftConsent table, (c) the possible values for the answer which will be shown to the user, as well as which answer is true and which is false - true or false will appear as the value in the additional_fields hash
  6. Once CSL has made the custom question live on your Staging/Prod CSL instance, check that it appears and test signing with both answers. Check that when the action appears in ID that it has the appropriate consents recorded against them (in the member_action_consents table).