Lifecycle Events

Duda will notify your backend about events that are part of the core app lifecycle: installation, upgrade, downgrade, uninstallation. Duda needs to know that your platform has received this notification before Duda can continue to the next step for the user. Therefore, the response of your endpoint does affect the behavior of the Duda platform. Duda will progress the user flow only after getting a 200 HTTP response code from your endpoint. The timeout to response to a lifecycle event is 60 seconds, but, realistically this should happen much faster. We have no retry policies for lifecycle events.

Events

All apps are automatically registered to lifecycle events to the endpoints set in the manifest. All fields are required.

Install

Duda sends lifecycle events to the endpoint specified in the manifest under installation_endpoint with a payload of the following structure:

{
  "auth": {
    "type": "bearer",
    "authorization_code": "XXX-XXXXX-XXXXX",
    "refresh_token": "YYY-YYYYY-YYYYY",
    "expiration_date": 1554254560438
  },
  "api_endpoint": "https://api-sandbox.duda.co",
  "installer_account_uuid": "10",
  "account_owner_uuid": "12",
  "user_lang": "en",
  "app_plan_uuid": "332653a3-df51-45ce-a873-fbb0b1ccb49f",
  "recurrency": "MONTHLY",
  "site_name": "1501ccca016a4220861ef07fe2c8eb0d",
  "free": true,
  "configuration_data": "{object}"
}

๐Ÿšง

API Endpoint - Multiple Duda Environments

You should store the api_endpoint in your database. This should be used, dynamically per install, to set where your App calls the Duda API from. Duda has multiple endpoints/hosts that will be sent here, so this cannot be a static value.

The app_plan_uuid is the plan the user selected to install. The recurrency is the payment recurrency (ANNUAL, MONTHLY, or NULL for free plans). The site_name is the identifier for the site on which the app was installed. It should be the same value as shown in the browser address bar. The configuration_data is an optional field used only when the application is installed via an API. It contains a JSON object which is passed as a parameter by the installer of the application.

The properties under auth are explained on App Store: Getting Started Guide, additional in-depth explanation is found on Authentication: Apps .

If the endpoint on installation_endpoint returned HTTP status code 200, Duda will finish the installation and direct the user to an iframe with the source specified on base_sso_url using the SSO link.

๐Ÿ“˜

The 'free' flag

Duda will send the free flag for installation which won't be paid. Your app should anticipate the free capabilities to avoid collecting payments on them. These would include:

  1. Installations on test environments.
  2. Installations generated by Duda employees for demo, sales, QA, and support cases.

Upgrade/Downgrade

Duda sends lifecycle events to the endpoint specified in the manifest under updowngrade_installation_endpoint with a payload of the following structure:

{
    "app_plan_uuid": "332653a3-df51-45ce-a873-fbb0b1ccb49f",
    "recurrency": "MONTHLY",
    "site_name": "1501ccca016a4220861ef07fe2c8eb0d"
}

The app_plan_uuid, recurrency, and site_name attributes are the same as for the installation callback. Duda would wait for a 200 HTTPS code response to finish the upgrade flow.

Uninstall

Duda sends lifecycle events to the endpoint specified in the manifest under uninstallation_endpoint with a payload of the following structure:

{
  "site_name":"1501ccca016a4220861ef07fe2c8eb0d",
  "free":false
}

Request Signature Validation

Duda will sign requests using the HMAC-SHA256 algorithm to allow your app to verify the authenticity of the sender (Duda) and the payload.

๐Ÿ“˜

Getting a secret key

To get your secret key, please contact [email protected].

The signature value is calculated as follows:

  1. Signature = base64(hmac-sha256(secret-key, timestamp + "." + message))

  2. Where timestamp is the value of the x-duda-signature-timestamp request header.

  3. message is the actual request body.

  4. "." is a string separating the 2 values.

  5. secret-key your unique key you received from Duda, decoded using base64 and UTF-8.

This calculation is done by Duda and the value is added to the webhook in the x-duda-signature request header.

The same calculation should be made by the application and compared against the header to assert the message was signed by us using your secret key.

Code Example

The request body is {'key1':'world','key2':'world'} the timestamp (x-duda-signature-timestamp header) is 1570350275357 and our secret key is mysecretsecret.

Calculating base64(hmac-sha256(secret-key, timestamp + "." + message)) results in +DCfT1wIMUiaZnlZB4u59/d5wkXKA89lv67Ov66vnyc= which is the value we will find in x-duda-signature header of the signed request.

const crypto = require('crypto');

const sig = req.headers["x-duda-signature"];
const time = req.headers["x-duda-signature-timestamp"];
const body = JSON.stringify(req.body);

const decoded = Buffer.from(<secret-key>, "base64").toString("utf8");
const check = crypto.createHmac("sha256", decoded)
	.update(`${time}.${body}`)
	.digest("base64");

assert(sig === check);
String getHMAC(String timestamp,String message) {
        String secretKey = ...;
        SecretKeySpec signingKey = new SecretKeySpec(secretKey.getBytes(StandardCharsets.UTF_8), "HmacSHA256");

        String input = timestamp + "." + message;
        Mac mac = Mac.getInstance("HmacSHA256");
        mac.init(signingKey);
        byte[] rawHmac = mac.doFinal(input.getBytes(StandardCharsets.UTF_8));
        return new String(Base64.getEncoder().encode((rawHmac)));
}


String body = request.getBody();
String timestamp = request.getHeader("x-duda-signature-timestamp");

String hmac = getHMAC(timestamp, body);
String signautreHeader = request.getHeader("x-duda-signature");

assert hmac.equals(signautreHeader);
require 'base64'
require 'openssl'

secret = 'your-secret'

decoded = Base64.strict_decode64(secret).force_encoding('UTF-8')

body = '{"key":"val"}'

digest = OpenSSL::Digest.new('sha256')

data = "timestamp" + "." + body

generated = OpenSSL::HMAC.digest(digest, decoded, data)

check = Base64.strict_encode64(generated).force_encoding('UTF-8')

check === 'x-duda-signature'