Subscription

The subscription API encapsulates the backend workflow of user subscription and un-subscription of push notification service. Depending on whether a API call comes from user browser as a user request or from an authorized server as an admin request, NotifyBC applies different validation rules. For user requests, the notification channel entered by user is unconfirmed. A confirmation code will be associated with this request. The confirmation code can be created in one of two ways:

  • by NotifyBC based on channel dependent subscription.confirmationRequest.<channel>.confirmationCodeRegex config.
  • by a trusted third party. This trusted third party encrypts the confirmation code using the public RSA key of the NotifyBC instance (see more about RSA Key Config) and pass the encrypted confirmation code to NotifyBC via user browser in the same subscription request. NotifyBC then decrypts to obtain the confirmation code. This method allows user subscribe to multiple notification services provided by NotifyBC instances in different trust domains (i.e. service providers) and only have to confirm the subscription channel once during one browser session. In such case only one NotifyBC instance should be chosen to deliver confirmation request to user.

Equipped with the confirmation code and a message template, NotifyBC can now send out confirmation request to unconfirmed subscription channel. At a minimum this confirmation request should contain the confirmation code. When user receives the message, he/she echos the confirmation code back to a NotifyBC provided API to verify against saved record. If match, the state of the subscription request is changed to confirmed.

For admin requests, NotifyBC can still perform the above confirmation process. But admin request has full CRUD privilege, including set the subscription state to confirmed, bypassing the confirmation process.

The workflow of user subscribing to notification services offered by a single service provider is illustrated by sequence diagram below. In this case, the confirmation code is generated by NotifyBC. single service provider subscription

In the case user subscribing to notifications offered by different service providers in separate trust domains, the confirmation code is generated by a third-party server app trusted by all NotifyBC instances. Following sequence diagram shows the workflow. The diagram indicates NotifyBC API Server 2 is chosen to send confirmation request.

multi service provider subscription

Model Schema

The API operates on following subscription data model fields:

NameAttributes

serviceName

name of the service. Avoid prefixing the name with underscore (_), or it may conflict with internal implementation.

typestring
requiredtrue

channel

name of the delivery channel. Valid values: email and sms. Notice inApp is invalid as in-app notification doesn't need subscription.

typestring
requiredtrue
defaultemail

userChannelId

user's delivery channel id, for example, email address

typestring
requiredtrue

id

subscription id

typestring, format depends on db
requiredfalse
auto-generatedtrue

state

state of subscription. Valid values: unconfirmed, confirmed, deleted

typestring
requiredfalse
defaultunconfirmed

userId

user id. Auto-populated for authenticated user requests.

typestring
requiredfalse

created

date and time of creation

typedate
requiredfalse
auto-generatedtrue

updated

date and time of last update

typedate
requiredfalse
auto-generatedtrue

confirmationRequest

an object containing these child fields
  • confirmationCodeRegex
    • type: string
    • regular expression used to generate confirmation code
  • confirmationCodeEncrypted
    • type: string
    • encrypted confirmation code
  • sendRequest
    • type: boolean
    • whether to send confirmation request
  • from, subject, textBody, htmlBody
    • type: string
    • these are email template fields used for sending email confirmation request. If confirmationRequest.sendRequest is true and channel is email, then these fields should be supplied in order to send confirmation email.
typeobject
requiredtrue for user request with encrypted confirmation code; false otherwise

broadcastPushNotificationFilter

a string conforming to jmespath filter expressions syntax after the question mark (?). The filter is matched against the data field of the subscription. Examples of filter
  • simple
    province == 'BC'
  • calling jmespath's built-in functions
    contains(province,'B')
  • calling custom filter functions
    contains_ci(province,'b')
  • compound
    (contains(province,'BC') || contains_ci(province,'b')) && city == 'Victoria'
All of above filters will match data object {"province": "BC", "city": "Victoria"}
typestring
requiredfalse

data

An object used by

data object can only be populated by non-anonymous requests.

typeobject
requiredfalse

unsubscriptionCode

generated randomly according to RegEx config anonymousUnsubscription.code.regex during anonymous subscription if config anonymousUnsubscription.code.required is set to true

typestring
requiredfalse
auto-generatedtrue

unsubscribedAdditionalServices

generated if parameter additionalServices is supplied in unsubscription request. Contains 2 sub-fields: ids and names, each being a list identifying the additional unsubscribed subscriptions.

typeobject
requiredfalse
auto-generatedtrue

Get Subscriptions

GET /subscriptions
  • permissions required, one of

    • super admin
    • admin
    • authenticated user
  • inputs

    • a filter containing properties where, fields, order, skip, and limit

      • parameter name: filter
      • required: false
      • parameter type: query
      • data type: object

      The filter can be expressed as either

      1. URL-encoded stringified JSON object (see example below); or
      2. in the format supported by qsopen in new window, for example ?filter[where][created][$gte]="2023-01-01"&filter[where][created][$lt]="2024-01-01"

      Regardless, the filter will have to be parsed into a JSON object conforming to

      {
          "where": {...},
          "fields": ...,
          "order": ...,
          "skip": ...,
          "limit": ...,
      }
      

      All properties are optional. The syntax for each property is documented, respectively

  • outcome

    • for admin requests, returns unabridged array of subscription data matching the filter
    • for authenticated user requests, in addition to filter, following constraints are imposed on the returned array
      • only non-deleted subscriptions
      • only subscriptions created by the user
      • the confirmationRequest field is removed.
    • forbidden for anonymous user requests
  • example

    to retrieve subscriptions created in year 2023, run

    curl -X GET --header 'Accept: application/json' 'http://localhost:3000/api/subscriptions?filter=%7B%22where%22%3A%7B%22created%22%3A%7B%22%24gte%22%3A%222023-01-01%22%2C%22%24lt%22%3A%222024-01-01%22%7D%7D%7D'
    

    the value of the filter query parameter is URL-encoded stringified JSON object

    {
      "where": {
        "created": {
          "$gte": "2023-01-01",
          "$lt": "2024-01-01"
        }
      }
    }
    

Get Subscription Count

GET /subscriptions/count
  • permissions required, one of

    • super admin
    • admin
    • authenticated user
  • inputs

    • a where query parameter with value conforming to MongoDB Query Documentsopen in new window

      • parameter name: where
      • required: false
      • parameter type: query
      • data type: object

      The value can be expressed as either

      1. URL-encoded stringified JSON object (see example below); or
      2. in the format supported by qsopen in new window, for example ?where[created][$gte]="2023-01-01"&where[created][$lt]="2024-01-01"
  • outcome

    Validations rules are the same as GET /subscriptions. If passed, the output is a count of subscriptions matching the query

    {
      "count": <number>
    }
    
  • example

    to retrieve the count of subscriptions created in year 2023, run

    curl -X GET --header 'Accept: application/json' 'http://localhost:3000/api/subscriptions/count?where=%7B%22created%22%3A%7B%22%24gte%22%3A%222023-01-01%22%2C%22%24lt%22%3A%222024-01-01%22%7D%7D'
    

    the value of the where query parameter is URL-encoded stringified JSON object

    {
      "created": {
        "$gte": "2023-01-01",
        "$lt": "2024-01-01"
      }
    }
    

Create a Subscription

POST /subscriptions
  • inputs

    • an object containing subscription data model fields. At a minimum all required fields that don't have a default value must be supplied. Id field should be omitted since it's auto-generated.
      • parameter name: data
      • required: true
      • parameter type: body
      • data type: object
  • outcome

    NotifyBC performs following actions in sequence

    1. inputs are validated. If validation fails, error is returned.

    2. for user requests, the state field is forced to unconfirmed

    3. for authenticated user request, userId field is populated with authenticated userId

    4. otherwise, unsubscriptionCode is generated if config subscription.anonymousUnsubscription.code.required is true, unless if the request is made by admin and the field is already populated

    5. if confirmationRequest.confirmationCodeEncrypted is populated, a confirmation code is generated by decrypting this field using private RSA key, then put decrypted confirmation code to field confirmationRequest.confirmationCode

    6. otherwise, for user requests and for admin requests missing message template, the message template is set to configured value. Then, if confirmationRequest.confirmationCodeRegex is populated, a confirmation code is generated conforming to regex and put to field confirmationRequest.confirmationCode

    7. the subscription request is saved to database.

    8. if confirmationRequest.sendRequest is true, then a message is sent to userChannelId. The message template is determined by

      1. if detectDuplicatedSubscription is true and there is already a confirmed subscription to the same serviceName, channel and userChannelId, then message is sent using duplicatedSubscriptionNotification as template;
      2. otherwise, a confirmation request is sent to using the template fields in confirmationRequest.

      Mail merge is performed on the template regardless.

    9. The subscription data, including auto-generated id, is returned as response unless there is error when sending confirmation request or saving to database. For user request, some fields containing sensitive information such as confirmationRequest are removed prior to sending the response.

  • examples

    1. To subscribe a user to service education, copy and paste following json object to the data value box in API explorer, change email addresses as needed, and click Try it out! button:
    {
      "serviceName": "education",
      "channel": "email",
      "userChannelId": "foo@bar.com"
    }
    

    As a result, foo@bar.com should receive an email confirmation request, and following json object is returned to caller upon sending the email successfully for admin request:

    {
      "serviceName": "education",
      "channel": "email",
      "userChannelId": "foo@bar.com",
      "state": "unconfirmed",
      "confirmationRequest": {
        "confirmationCodeRegex": "\\d{5}",
        "sendRequest": true,
        "from": "no_reply@bar.com",
        "subject": "confirmation",
        "textBody": "Enter {confirmation_code} on screen",
        "confirmationCode": "45304"
      },
      "created": "2016-10-03T17:35:40.202Z",
      "updated": "2016-10-03T17:35:40.202Z",
      "id": "57f296ec7eead50554c61de7"
    }
    

    For non-admin request, the field confirmationRequest is removed from response, and field userId is populated if request is authenticated:

    {
      "serviceName": "education",
      "channel": "email",
      "userChannelId": "foo@bar.com",
      "state": "unconfirmed",
      "userId": "<user_id>",
      "created": "2016-10-03T18:17:09.778Z",
      "updated": "2016-10-03T18:17:09.778Z",
      "id": "57f2a0a5b1aa0e2d5009eced"
    }
    
    1. To subscribe a user to service education with RSA public key encrypted confirmation code supplied, POST following request

      {
        "serviceName": "education",
        "channel": "email",
        "userChannelId": "foo@bar.com",
        "confirmationRequest": {
          "confirmationCodeEncrypted": "<encrypted-confirmation-code>",
          "sendRequest": true,
          "from": "no_reply@bar.com",
          "subject": "confirmation",
          "textBody": "Enter {confirmation_code} on screen"
        }
      }
      

      As a result, NotifyBC will decrypt the confirmation code using the private RSA key, replace placeholder {confirmation_code} in the email template with the confirmation code, and send confirmation request to foo@bar.com.

Verify a Subscription

GET /subscriptions/{id}/verify
  • inputs

    • subscription id
      • parameter name: id
      • required: true
      • parameter type: path
      • data type: string
    • confirmation code
      • parameter name: confirmationCode
      • required: true
      • parameter type: query
      • data type: string
    • whether or not replacing existing subscriptions
      • parameter name: replace
      • required: false
      • parameter type: query
      • data type: boolean
  • outcome

    NotifyBC performs following actions in sequence

    1. the subscription identified by id is retrieved
    2. for user request, the userId of the subscription is checked against current request user, if not match, error is returned; otherwise
    3. input parameter confirmationCode is checked against confirmationRequest.confirmationCode. If not match, error is returned; otherwise
    4. if input parameter replace is supplied and set to true, then existing confirmed subscriptions from the same serviceName, channel and userChannelId are deleted. No unsubscription acknowledgement notification is sent
    5. state is set to confirmed
    6. the subscription is saved back to database
    7. displays acknowledgement message according to configuration
  • example

    to verify a subscription with id abc, confirmation code 12345, and delete existing confirmed subscriptions once verified, run

    curl 'http://localhost:3000/api/subscriptions/abc/verify?confirmationCode=12345&replace=true'
    

Update a Subscription

PATCH /subscriptions/{id}

This API is used by authenticated user to change user channel id (such as email address) and resend confirmation code.

  • permissions required, one of

    • super admin
    • admin
    • authenticated user
  • inputs

    • subscription id
      • parameter name: id
      • required: true
      • parameter type: path
      • data type: string
    • an object containing fields to be updated.
      • parameter name: data
      • required: true
      • parameter type: body
      • data type: object
  • outcome

    NotifyBC processes the request similarly as creating a subscription except during input validation it imposes following extra constraints to user request

    • only fields userChannelId, state and confirmationRequest can be updated
    • when changing userChannelId, confirmationRequest must also be supplied
    • if userChannelId is different from the saved record, state is forced to unconfirmed.

Delete a Subscription (unsubscribing)

DELETE /subscriptions/{id}?unsubscriptionCode={unsubscriptionCode}&additionalServices[]={additionalServices}&userChannelId={userChannelId}
or
GET /subscriptions/{id}/unsubscribe?unsubscriptionCode={unsubscriptionCode}&additionalServices[]={additionalServices}&userChannelId={userChannelId}
  • inputs

    • subscription id
      • parameter name: id
      • required: true
      • parameter type: path
      • data type: string
    • unsubscription code for anonymous request
      • parameter name: unsubscriptionCode
      • required: false
      • parameter type: query
      • data type: string
    • additional service names to unsubscribe
      • parameter name: additionalServices
      • required: false
      • parameter type: query
      • data type: array of strings. If there is only one item and its value is _all, then all services the user subscribed on this NotifyBC instance are included. Supply multiple items by repeating this query parameter.
    • user channel id for extended validation
      • parameter name: userChannelId
      • required: false
      • parameter type: query
      • data type: string
  • outcome

    NotifyBC performs following actions in sequence

    1. the subscription identified by id is retrieved
    2. for user request,
    • if request is authenticated, the userId of the subscription is checked against current request user, if not match, request is rejected
    • if request is anonymous, and server is configured to require unsubscription code, the input unsubscription code is matched against the unsubscriptionCode field. Request is rejected if not match. In addition, if input parameter userChannelId is populated but doesn't match, request is rejected
    1. if the subscription state is not confirmed, request is rejected
    2. if additionalServices is populated, database is queried to retrieve the serviceName and id fields of the additional subscriptions
    3. the field state is set to deleted for the subscription identified by id as well as additional subscriptions retrieved in previous step
    4. if additionalServices is not empty, the service names and ids of the additional subscriptions are added to field unsubscribedAdditionalServices of the subscription identified by id to allow bulk undo unsubscription later on
    5. for anonymous unsubscription, an acknowledgement notification is sent to user if configured so
    6. returns
    • for anonymous request, either the message or redirect as configured in anonymousUnsubscription.acknowledgements.onScreen
    • for authenticated user or admin requests, number of records affected or error message if occurred.
  • examples

    1. To allow an anonymous subscriber to unsubscribe single subscription, provide url token {unsubscription_url} in notification messages. When sending notification, mail merge is performed on the token resolving to the GET API url and parameters.
    2. To allow an anonymous subscriber to unsubscribe all subscriptions, provide url token {unsubscription_all_url} in notification messages.

Un-deleting a Subscription

GET /subscriptions/{id}/unsubscribe/undo

This API allows an anonymous subscriber to undo an unsubscription.

  • inputs

    • subscription id
      • parameter name: id
      • required: true
      • parameter type: path
      • data type: string
    • unsubscription code
      • parameter name: unsubscriptionCode
      • required: false
      • parameter type: query
      • data type: string
  • outcome

    NotifyBC performs following actions in sequence

    1. the subscription identified by id is retrieved
    2. for user request,
    • if request is anonymous, and server is configured to require unsubscription code, the input unsubscription code is matched against the unsubscriptionCode field. Request is rejected if not match
    • if request is authenticated, request is rejected
    • if the subscription state is not deleted, request is rejected
    1. the field state is set to confirmed for the subscription identified by id as well as additional subscriptions identified in field unsubscribedAdditionalServices, if populated
    2. field unsubscribedAdditionalServices is removed if populated
    3. returns either the message or redirect as configured in anonymousUndoUnsubscription
  • example

    To allow an anonymous subscriber to undo unsubscription, provide link token {unsubscription_reversion_url} in unsubscription acknowledgement notification, which is by default set. When sending notification, mail merge is performed on this token resolving to the API url and parameters.

Get all services with confirmed subscribers

GET /subscriptions/services

This API is designed to facilitate implementing autocomplete for admin web console.

  • permissions required, one of
    • super admin
    • admin
  • inputs - none
  • outcome
    • for admin requests, returns an array of unique service names with confirmed subscribers
    • forbidden for non-admin requests

Replace a Subscription

PUT /subscriptions/{id}

This API is intended to be only used by admin web console to modify a subscription without triggering any confirmation or acknowledgement notification.

  • permissions required, one of
    • super admin
    • admin
  • permissions required, one of
    • super admin
    • admin
    • authenticated user
  • inputs
    • subscription id
      • parameter name: id
      • required: true
      • parameter type: path
      • data type: string
    • subscription data
      • parameter name: data
      • required: true
      • parameter type: body
      • data type: object
  • outcome
    • for admin requests, replace subscription identified by id with parameter data and save to database. No notification is sent.
    • forbidden for non-admin requests