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.
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.
Model Schema
The API operates on following subscription data model fields:
Name | Attributes | ||||||
---|---|---|---|---|---|---|---|
serviceName name of the service. Avoid prefixing the name with underscore (_), or it may conflict with internal implementation. |
| ||||||
channel name of the delivery channel. Valid values: email and sms. Notice inApp is invalid as in-app notification doesn't need subscription. |
| ||||||
userChannelId user's delivery channel id, for example, email address |
| ||||||
id subscription id |
| ||||||
state state of subscription. Valid values: unconfirmed, confirmed, deleted |
| ||||||
userId user id. Auto-populated for authenticated user requests. |
| ||||||
created date and time of creation |
| ||||||
updated date and time of last update |
| ||||||
confirmationRequest an object containing these child fields
|
| ||||||
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
|
| ||||||
An object used by
|
| ||||||
unsubscriptionCode generated randomly according to RegEx config anonymousUnsubscription.code.regex during anonymous subscription if config anonymousUnsubscription.code.required is set to true |
| ||||||
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. |
|
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
- URL-encoded stringified JSON object (see example below); or
- in the format supported by qs, 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
- for where , see MongoDB Query Documents
- for fields , see Mongoose select
- for order, see Mongoose sort
- for skip, see MongoDB cursor.skip
- for limit, see MongoDB cursor.limit
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 Documents
- parameter name: where
- required: false
- parameter type: query
- data type: object
The value can be expressed as either
- URL-encoded stringified JSON object (see example below); or
- in the format supported by qs, 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
- 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.
outcome
NotifyBC performs following actions in sequence
inputs are validated. If validation fails, error is returned.
for user requests, the state field is forced to unconfirmed
for authenticated user request, userId field is populated with authenticated userId
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
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
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
the subscription request is saved to database.
if confirmationRequest.sendRequest is true, then a message is sent to userChannelId. The message template is determined by
- 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;
- otherwise, a confirmation request is sent to using the template fields in confirmationRequest.
Mail merge is performed on the template regardless.
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
- 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" }
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
- subscription id
outcome
NotifyBC performs following actions in sequence
- the subscription identified by id is retrieved
- for user request, the userId of the subscription is checked against current request user, if not match, error is returned; otherwise
- input parameter confirmationCode is checked against confirmationRequest.confirmationCode. If not match, error is returned; otherwise
- 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
- state is set to confirmed
- the subscription is saved back to database
- 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
- subscription id
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
- subscription id
outcome
NotifyBC performs following actions in sequence
- the subscription identified by id is retrieved
- 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
- if the subscription state is not confirmed, request is rejected
- if additionalServices is populated, database is queried to retrieve the serviceName and id fields of the additional subscriptions
- the field state is set to deleted for the subscription identified by id as well as additional subscriptions retrieved in previous step
- 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
- for anonymous unsubscription, an acknowledgement notification is sent to user if configured so
- 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.
- 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.
- 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
- subscription id
outcome
NotifyBC performs following actions in sequence
- the subscription identified by id is retrieved
- 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
- the field state is set to confirmed for the subscription identified by id as well as additional subscriptions identified in field unsubscribedAdditionalServices, if populated
- field unsubscribedAdditionalServices is removed if populated
- 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
- subscription id
- 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