Webhooks

Cycle provides webhooks to let you create automations based on specific events occurring on data changes.

You'll receive HTTP push notification whenever data is updated or created. Rather than polling continuously a specific entity, you can listen to changes and get notified when something happened.

Our webhooks are specific to a product and based on specific ressources. The HTTP push requests always follow the same logic: type is the concatenation of the entity you are listening to and the action happening. id is the id of the entity itself and the other ids are used as complementary information. Every push request will also contain the productId which refers to the workspace the action is happening into.

The ressources you can listen to and their corresponding typical push requests are listed below.

Ressources and events

DOC_CREATED

For every new doc

{
    type: "doc.create",
    id: "<docId>",
    doctypeId: "<doctypeId>",
    productId: "<productId>",
}

COMMENT_CREATED

For every new comment

{
    type: "comment.create",
    id: "<commentId>",
    docId: "<docId>",
    doctypeId: "<doctypeId>",
    productId: "<productId>",
}

STATUS_CHANGED

For every doc status change

{
    type: "status.change",
    id: "<statusId>",
    previousStatusId: "<previousStatusId>",
    docId: "<docId>",
    doctypeId: "<doctypeId>",
    productId: "<productId>",
}

VALUE_CHANGED

For every doc value change (i.e: update, create, delete)

{
    type: "value.change",
    id: "<valueId>",
    value: "<valueText>"
    previousValueId: "<previousValueId>", // @NB: only displayed for single select changes and value removal
    previousValue: "<previousValueText>", // @NB: only displayed for single select changes and value removal
    propertyId: "<propertyId>",
    property: "<propertyName>",
    propertyType: "<propertyType>",
    docId: "<docId>",
    doctypeId: "<doctypeId>",
    productId: "<productId>",
}

CHANGELOG_SUBSCRIBED

For every subscription to the changelog

{
    type: "changelog.subscribe",
    email: "<userEmail>",
    changelogId: "<changelogId>",
    productId: "<productId>",
}

Manipulate webhooks in Cycle

Everything can be handled with our graphql API. For the sake of simplicity we will only cover the creation and listing of webhooks in this tutorial but the mutations are self explanatory and can be manipulated in the playground.

Create a new webhook

mutation createWebhook {
  createWebhook(
    productId: "<productId>"
    url: "https://mysupersite.com"
    label: "my webhook"
    ressources: [DOC_CREATED]
  ) {
    id
    label
    url
    ressources
  }
}

List your webhooks

query getProduct {
  getProductBySlug(slug: "<slug>") {
    id
    webhooks {
      edges {
        node {
          id
          label
          url
          ressources
        }
      }
    }
  }
}

Listening for events

Now that we have created our webhook, we can start receiving notifications! To do so, it is recommended to have a tunneling service such as ngrok in order redirect https request to your local server. In production mode, simply use your api url.

In this simple tutorial we won't setup a local server but only a tunnel to observe the incoming event. The next steps will show you how to do so:

  1. Download ngrok based on your OS

  2. run the ngrok http 80 command to start a tunnel

  3. trigger the createWebhook mutation with your https tunnel url as the url (on the screenshot above, we can see that our tunnel url is https://8335dc9a3887.ngrok.app)

    mutation createWebhook {
      createWebhook(
        productId: "<productId>"
        url: "https://8335dc9a3887.ngrok.app"
        label: "my ngrok webhook"
        ressources: [DOC_CREATED]
      ) {
        id
        label
        url
        ressources
      }
    }

  4. create a doc in your Cycle workspace with the createFeedback mutation

    mutation createFeedback {
        createFeedback(
            productId: "<productId>"
            title: "feedback to trigger a webhook"
            customer: "customer@mail.com"
            source: "https://mysupersite.com"
        ) {
            id
            title
        }
    }

  5. 3 POST requests were made because of the Retry logic (you should always answer with an HTTP 200 response). If you want to see more details about the request, open up the Web interface proposed by ngrok on http://127.0.0.1:4040 (in this example). The content of the request is as explained in DOC_CREATED section.

  • It's available in a publicly accessible HTTPS, non-localhost URL

  • It will respond to the Cycle Webhook push (HTTP POST request) with a HTTP 200 ("OK") response

Retry logic

If a delivery fails (i.e. your server is unavailable or responded with a non-200 HTTP status code), the push will be retried a couple of times. Right now an exponential backoff delay is used: the attempt will be retried 3 times after approximately 5 minutes then 10 minutes then 20 minutes.

Verifying the webhook signature

You should always verify your webhook signature to make sure the payload was sent by Cycle. To do so we hash the body of the request with a SHA256 HMAC signature. The signature is contained in Cycle-Signature header. To verify it, calculate the signature on your side and compare it with the one from the header. Below is a javascript example of the verification:

const signature = crypto
                .createHmac("sha256", WEBHOOK_SECRET)
                .update(body)
                .digest("base64");
if (signature !== request.headers['cycle-signature']) throw "Invalid signature";

Last updated