Envoy::Hooks

Note: this Readme is currently written as if the API is completed. This is so that we can get the desired interface and work backwards to create it.

Once everything is implemented, this note will be removed.

This is a Ruby wrapper for the Envoy Hooks service.

It has the following three API methods regarding UI Hooks and their subscriptions:

  • upsertSubscription
  • triggerUIHooks
  • triggerZoneUIHooks
  • subscribedUIHooks

These are the methods listed in the Jira ticket describing what's required for the email customization screen and the dynamic email sender.

The following method is used for syncing the clientConfig -- that is, the configuration for the app/integration.

  • syncClientConfig

That covers all the endpoints that are currently available on the Hooks Service. The number of endpoints, however, will likely grow.

Installation

Add this line to your application's Gemfile:

gem 'envoy-hooks'

And then execute:

$ bundle install

Or install it yourself as:

$ gem install envoy-hooks

Usage

Setting up JWT

Before we can initialize the hooks service, we need to get a valid JWT token.

These instructions will be focused on envoy-web for now. When we want to use this elsewhere, we can update them to be more generic.

1. Set up RSA key in hooks service

  • Grab RSA key from dev.pub.pem in envoy-web
  • Replace newlines with \n, so it's all on one line
  • Surround with quotes
  • Put in ENVOY_PUBLIC_KEY for hooks service's .env file ()

2. Get a valid JWT

In envoy-web, use rails c and run the following commands to generate a client record for the Hooks service, and to get a valid JWT.

> client = Client.create(name: "Envoy Hooks", permitted_scopes: ["client.privileged", "first-party"])
> subject = Envoy::Auth::Subject.new(client)
> jwt_data = Envoy::Auth::TokenGenerator.new(client.id, subject, client.permitted_scopes).generate
> jwt_token = jwt_data[:access_token]

You'll use this JWT token when initializing the hooks service.

Initalizing the Hooks Service

You must initialize the Hooks service before calling any methods.

hooksService = Envoy::Hooks.new(
  # REQUIRED
  jwtToken: "2c3671da-ad83-44a5-9798-cdbc0ee946b6", # what you generated in the previous section
  baseUrl: "http://localhost:9000", # should differ based on env

  # OPTIONAL
  adapter: Faraday.default_adapter, # You probably won't need to mess with this in typical usage.  But if you do, a list is here: https://github.com/lostisland/awesome-faraday/#adapters
  stubs: [], # These are only used during testing
)

We'll use the hooksService variable defined here in the rest of the documentation.

UI Hooks and Subscriptions

subscribedUIHooks

This returns a list of UI Hooks that your location is connected to, optionally filtered by trigger name (extension point). This will pull in hooks from company-level subscriptions as well.

hooksService.subscribedUIHooks(
  # REQUIRED
  locationID: 3,

  # OPTIONAL
  triggerName: "welcome_email", # which "extension point" we're fetching hooks for.  If left empty, we won't filter by triggerName.
  includeHidden: true, # include hooks that have been hidden.  Defaults to false.
)

The return value will be an array of the subscribed UI Hooks.

They'll look like this:

{
  uiHookId: 3581
  # 'client' is the app you're calling this from
  clientId: 28567
  clientName: "Cool Envoy Customer #28567"
  triggerName: "welcome_email"
  invocationType: "HTML"
  label: "Parking QR Code" # the display name of the UI Hook
  isHidden: false
  icon: "https://envoy.com/images/logo-new.svg" # The app-level icon - determined per integration, not per hook
}

upsertSubscription

Create or update a subscription.

A subscription is when a location is associated with an integration - typically when they install the integration. This method will also be called when they perform the setup steps, and when they disable the integration.

Keep in mind that "updated" subscriptions completely replace the previous subscription, meaning a null value in the new subscription data will be a null value in the database, not the old value for that field.

hooksService.upsertSubscription(
  # REQUIRED
  subscriptionScope: {
    type: "LOCATION", # or COMPANY or USER
    id: 52952 # the ID of the location or company
  },
  clientId: 39581, # the ID of the integration
  status: "ENABLED", # could also be DISABLED or PENDING

  # OPTIONAL
  config: {}, # TODO - what can go in here?
  externalId: 20958, # Application-specific ID, e.g. an app install ID
  creatorUserId: 9824, # ID of the Envoy user that made the subscription, if any
  authorizedScopes: [], # The scopes that the creator authorized this client to have, if any.
)

When you send a valid upsert, an updated subscription object will be returned.

The subscription object is the same as the arguments to upsertSubscription with two differences:

  • there's an ID field
  • subscriptionScope object is split into scopeType and scopeId, which are directly on the subscription object.

triggerUIHooks

This is how we fetch custom UI. The name "trigger UI Hooks" comes from the fact that we are getting the custom UI from potentially multiple UI Hooks, and we "trigger" them in order to fetch the data.

This will act to fetch the custom UI, which we will receive in the response.

hooksService.triggerUIHooks(
  # REQUIRED
  locationId: 3,
  triggerName: "welcome_email", # The UI Hook name to invoke (deprecated)
  triggerNames: ["welcome_email"], # The list of UI Hook extension names to invoke (triggerName or triggerNames required)
  responseType: "JSON", # only other option is HTML.  Determines whether you get Component JSON or prerendered HTML.
  resourceId: 341153, # the ID of the resource ( entry or invite ) we are working with, only required if previewMode = false

  # OPTIONAL
  previewMode: false, # Whether or not to run in preview mode.  Preview mode makes it so you do not need to send in a resourceId
  accentColor: "#123DEF", # Override the default Envoy accent color on applicable UI components.  Currently only in emails, but that could change.
  uiHookIds: [2954, 8271], # The specific UI hooks to invoke. If you don't include it, _all_ UI Hook subscriptions that match the locationId and triggerName will trigger.
)

The return value will be an array of objects. Each object will contain a lot of information, but the most important part is response - that will contain the HTML or JSON from the UI Hooks, which will then be used to create the UI.

triggerZoneUIHooks

This is how we fetch custom UI at a Connect zone. The name "trigger Zone UI Hooks" comes from the fact that we are getting the custom UI from potentially multiple UI Hooks, and we "trigger" them in order to fetch the data.

This will act to fetch the custom UI, which we will receive in the response.

hooksService.triggerZoneUIHooks(
  # REQUIRED
  zoneIds: [3],
  triggerName: "welcome_email", # The UI Hook name to invoke (deprecated)
  triggerNames: ["welcome_email"], # The list of UI Hook extension names to invoke (triggerName or triggerNames required)
  responseType: "JSON", # only other option is HTML.  Determines whether you get Component JSON or prerendered HTML.

  # XOR REQUIRED
  resourceId: 341153, # the ID of the invite we are working with
  resourceIds: [341153, 341153] # array of IDs of the invites we are working with

  # OPTIONAL
  previewMode: false, # Whether or not to run in preview mode.  Preview mode makes it so you do not need to send in a resourceId
  accentColor: "#123DEF", # Override the default Envoy accent color on applicable UI components.  Currently only in emails, but that could change.
  uiHookIds: [2954, 8271], # The specific UI hooks to invoke. If you don't include it, _all_ UI Hook subscriptions that match the locationId and triggerName will trigger.
)

The return value will be an array of objects. Each object will contain a lot of information, but the most important part is response - that will contain the HTML or JSON from the UI Hooks, which will then be used to create the UI.

Client Config

Client Config object

All of the Client Config related methods return a Client Config object - aka, the configuration of your integration/app.

Here's an example Client Config object:

{
  clientId: "87305", # UUID for this record
  clientSecret: "ac33c458-25ee-45e1-ac19-cd8e55e4261c", # used to sign invocations
  clientName: "Parking Services Inc.",
  clientType: "APP", # Other option is PRODUCT
  clientIcon: "https://envoy.com/images/logo-new.svg", # The app-level icon - determined per integration, not per hook
  version: "1.8.2", # Application-specific version. The hooks service does not store all versions, just the one you've given us.
  subscriptionEnabledInvocationURL: "https://myapp.thirdparty.com/webhooks/subscription-status/subscribed", # Invoked when a client is subscribed to a location.
  subscriptionDisabledInvocationURL: "https://myapp.thirdparty.com/webhooks/subscription-status/unsubscribed", # Invoked when a client is unsubscribed from a location.
  externalId: "8739534", # Application-specific ID, e.g. an app id.
  isDisabled: false, # Disabling the integration hides it from your company.  Enabling it shares it with your company.

  eventHooks: [{
    id: "9896886",
    triggerNames: ["visitor_sign_in"], # Must contain a valid event hook name
    invocationURL: "https://www.my-app.com/webhooks/envoy/visitor-sign-in", # The URL to invoke when this hook is triggered.
    invocationFormatVersion: "V2", # Currently only V1 and V2.  Allows app developers to migrate from the old format to the new one, and help us introduce new formats in the future if needed.

  }], # 0 to N hooks allowed
  featureHooks: [], # 0 to N hooks allowed.  Feature hooks format is exact same as event hooks format, except that the triggerName must be a valid feature hook instead of an event hook, so left it off to save room
  uiHooks: [{
    id: "198968",
    triggerNames: ["welcome_email"], # which "extension points" the UI will be showing up at.
    invocationType: "DYNAMIC_CONTENT", # Currently can be DYNAMIC_CONTENT or STATIC_CONTENT.  List will be expanded in Q1 2022.
    invocationURL: "https://www.my-app.com/webhooks/envoy/visitor-invite-email-content", # The URL where we get
    invocationFormatVersion: "V2", # Currently only V1 and V2.  Allows app developers to migrate from the old format to the new one, and help us introduce new formats in the future if needed.
    staticContent: nil, # Component JSON, only returned if invocationType is STATIC_CONTENT.
    label: "Parking Pass - QR Code",

  }], # once again, 0 to N hooks allowed.  Note that this shares some fields with the event hooks and feature hooks but also has additional fields
}

syncClientConfig

Update the Client Config - or, if none exists with that clientId, create a new one.

Note that the update is a complete replacement - so if you have a value in the existing config, and you put a nil value here, then the value in the existing config will be overwritten with nil.

The arguments are the same properties as the return value, so I will remove the comments in the example.

hooksService.syncClientConfig(
  # REQUIRED
  clientId: "87305",
  clientSecret: "ac33c458-25ee-45e1-ac19-cd8e55e4261c",
  clientName: "Parking Services Inc.",
  clientType: "APP",
  clientIcon: "https://envoy.com/images/logo-new.svg",
  version: "1.8.2",
  isDisabled: false,

  # OPTIONAL
  subscriptionEnabledInvocationURL: "https://myapp.thirdparty.com/webhooks/subscription-status/subscribed",
  subscriptionDisabledInvocationURL: "https://myapp.thirdparty.com/webhooks/subscription-status/unsubscribed",
  externalId: "8739534",

  eventHooks: [{
    id: "9896886",
    triggerNames: ["visitor_sign_in"],
    invocationURL: "https://www.my-app.com/webhooks/envoy/visitor-sign-in",
    invocationFormatVersion: "V2",
  }],
  featureHooks: [],
  uiHooks: [{
    id: "198968",
    triggerNames: ["welcome_email"],
    invocationType: "DYNAMIC_CONTENT",
    invocationURL: "https://www.my-app.com/webhooks/envoy/visitor-invite-email-content",
    invocationFormatVersion: "V2",
    staticContent: nil,
    label: "Parking Pass - QR Code",
  }],
)

The return value is the newly created or updated Client Config.

Timeouts

The default timeout is 25 seconds. This is because the Heroku timeout is at 30 seconds, and a Heroku timeout error will give us less information than if we can trace the timeout to a method on this gem.

If you wish to change the timeout length, that is an option when initializing Envoy::Hooks.

Envoy::Hooks.new(timeout: 3).triggerUIHooks(/*...*/)

The units are in seconds.

Development

This was autogenerated, so I assume it's correct. If it's not, please make a PR fixing it or at tell me or least create a Github issue.

After checking out the repo, run bin/setup to install dependencies. Then, run rake spec to run the tests. You can also run bin/console for an interactive prompt that will allow you to experiment.

To install this gem onto your local machine, run bundle exec rake install. To release a new version, update the version number in version.rb, and then run bundle exec rake release, which will create a git tag for the version, push git commits and the created tag, and push the .gem file to rubygems.org.

Future of this gem

Right now this is a handcrafted gem. In the future we hope to autogenerate it, so that it's automatically kept up to date and can be easily generated in other languages and for other codebases. If we switch to the autogenerated gem, we intend to keep the API changes minimal.