Webhooks

You can register webhooks on Tggl to call endpoints of your choice when a flag is updated. You can customize the URL and headers of the request.

Webhooks are mainly useful for two use-cases:

  • Monitor changes on flags and display a vertical indicator on your charts to correlate metric changes with flags updates.
  • Maintain a cache of you flags configuration up-to-date to evaluate flags locally without doing any API calls.

In both cases it is up to you to implement the logic on your side.

When are webhooks called?

All webhooks of your organization are called when a flag is updated in any of you projects.

If you want to be notified only when a flag is updated in a specific project, you have to implement some logic on your side by checking the content of the request's body.

Authentication

You must use a header to authenticate the request. Most of the time you will probably want to use an Authorization header with a Bearer:

Webhook auth 2x

Debugging

You can debug your webhooks by sending a test request to your endpoint and displaying the list of calls. For each call you can inspect the request and the response.

Webhook runs 2x

Request body

The request is always a POST to the endpoint you specified with the following body format:

{
  "flagSlug": "my_feature",
  "userEmail": "john.doe@gmail.com",
  "projectSlug": "my_project",
  "codeLikeBefore": "IF ... THEN ... ELSE ...",
  "codeLikeAfter": "IF ... THEN ... ELSE ...",
  "changes": [
    { "__typename":  "FlagConditionCreated", /*...*/ },
    { "__typename":  "FlagDescriptionChanged", /*...*/ }
  ]
}

flagSlug

The flagSlug key is the slug of the flag that was updated, you can find it on the app:

Flags slugs 2x

projectSlug

The projectSlug key is the slug of the project of the flag and can also be found on the app:

Project slug 2x

userEmail

The email of the user who updated the flag.

codeLikeBefore and codeLikeAfter

The codeLikeBefore and codeLikeAfter keys are a string representation of the flag's conditions before and after the change. You can use them to display it directly to humans and even display a diff of the change. The code-like representation looks like this:

IF
  userId IS ONE OF [
    "foo",
    "bar",
  ]
THEN
  Live (value: true)
ELSE IF
  environment == "production"
  AND 20% OF userId (0% TO 20%)
  AND timestamp >= "2023-12-11T00:00"
THEN
  Live (value: true)
ELSE
  Off (inactive)

Note that the code-like representation should not be parsed, it is only meant to be displayed to humans.

changes

The changes key is an array of changes that were applied to the flag. Each change is an object with a __typename key that can be one of the following values:

FlagActivated
FlagArchived
FlagConditionCreated
FlagConditionDeleted
FlagConditionUpdated
FlagCreated
FlagDefaultVariationChanged
FlagDescriptionChanged
FlagNameChanged
FlagSharingChanged
FlagSlugChanged
FlagTagsChanged
FlagVariationCreated
FlagVariationDeleted
FlagVariationUpdated

Based on the __typename you can know which fields are available on the change object:

TypeScript definition
// Common types definition ----------------
 
type Variation = {
  id: string
  name: string
  description: string
  color: string
  active: boolean
  value: any
  order: number
}
 
type Operator =
  | 'ARR_NOT_OVERLAP'
  | 'ARR_OVERLAP'
  | 'DATE_AFTER'
  | 'DATE_BEFORE'
  | 'DATE_IS'
  | 'IS_EMPTY'
  | 'IS_FALSE'
  | 'IS_NOT_EMPTY'
  | 'IS_TRUE'
  | 'NUM_EQUALS'
  | 'NUM_GT'
  | 'NUM_GTE'
  | 'NUM_LT'
  | 'NUM_LTE'
  | 'NUM_NOT_EQUALS'
  | 'PERCENTAGE'
  | 'SELECT_EQUALS'
  | 'SELECT_NOT_EQUALS'
  | 'SEMVER_GTE'
  | 'SEMVER_IS'
  | 'SEMVER_LTE'
  | 'STR_AFTER'
  | 'STR_BEFORE'
  | 'STR_CONTAINS'
  | 'STR_ENDS_WITH'
  | 'STR_EQUALS'
  | 'STR_NOT_CONTAINS'
  | 'STR_NOT_ENDS_WITH'
  | 'STR_NOT_EQUALS'
  | 'STR_NOT_REGEXP'
  | 'STR_NOT_STARTS_WITH'
  | 'STR_REGEXP'
  | 'STR_STARTS_WITH'
 
type Condition = {
  id: string
  name: string
  variation: Variation
  rules: {
    [id: string]: {
      order: number
      operator: Operator
      params: any
      contextProperty: {
        id: string;
        name: string;
        key: string
      }
    }
  }
  codeLike: string
  order: number
}
 
type Tag = {
  id: string
  name: string
  color: string
}
 
// Changes definition ----------------
 
type FlagCreated = {
  __typename: "FlagCreated"
  created: true
}
 
type FlagArchived = {
  __typename: "FlagArchived"
  oldArchived: boolean
  newArchived: boolean
}
 
type FlagActivated = {
  __typename: "FlagActivated"
  oldActive: boolean
  newActive: boolean
}
 
type FlagSlugChanged = {
  __typename: "FlagSlugChanged"
  oldSlug: string
  newSlug: string
}
 
type FlagNameChanged = {
  __typename: "FlagNameChanged"
  oldName: string
  newName: string
}
 
type FlagDescriptionChanged = {
  __typename: "FlagDescriptionChanged"
  oldDescription: string
  newDescription: string
}
 
type FlagSharingChanged = {
  __typename: "FlagSharingChanged"
  oldShareWithEveryone: boolean
  newShareWithEveryone: boolean
  oldRoles: string[]
  newRoles: string[]
  addedRoles: string[]
  removedRoles: string[]
}
 
type FlagTagsChanged = {
  __typename: "FlagTagsChanged"
  oldTags: Tag[]
  newTags: Tag[]
  addedTags: Tag[]
  removedTags: Tag[]
}
 
type FlagVariationCreated = {
  __typename: "FlagVariationCreated"
  variation: Variation
}
 
type FlagVariationDeleted = {
  __typename: "FlagVariationDeleted"
  variation: Variation
}
 
type FlagVariationUpdated = {
  __typename: "FlagVariationUpdated"
  oldVariation: Variation
  newVariation: Variation
}
 
type FlagDefaultVariationChanged = {
  __typename: "FlagDefaultVariationChanged"
  oldVariation: Variation
  newVariation: Variation
}
 
type FlagConditionCreated = {
  __typename: "FlagConditionCreated"
  condition: Condition
}
 
type FlagConditionDeleted = {
  __typename: "FlagConditionDeleted"
  condition: Condition
}
 
type FlagConditionUpdated = {
  __typename: "FlagConditionUpdated"
  oldCondition: Condition
  newCondition: Condition
}