Page MenuHomePhabricator

User Guide: Webhooks
Phabricator User Documentation (Application User Guides)

Guide to configuring webhooks.

Overview

If you'd like to react to events in Phabricator or publish them into external systems, you can configure webhooks.

Configure webhooks in HeraldWebhooks. Users must have the "Can Create Webhooks" permission to create new webhooks.

Triggering Hooks

Webhooks can be triggered in two ways:

  • Set the hook mode to Firehose. In this mode, your hook will be called for every event.
  • Set the hook mode to Enabled, then write Herald rules which use the Call webhooks action to choose when the hook is called. This allows you to choose a narrower range of events to be notified about.

Testing Hooks

To test a webhook, use New Test Request from the web interface.

You can also use the command-line tool, which supports a few additional options:

phabricator/ $ ./bin/webhook call --id 42 --object D123

Verifying Requests

When your webhook callback URI receives a request, it didn't necessarily come from Phabricator. An attacker or mischievous user can normally call your hook directly and pretend to be notifying you of an event.

To verify that the request is authentic, first retrieve the webhook key from the web UI with View HMAC Key. This is a shared secret which will let you verify that Phabricator originated a request.

When you receive a request, compute the SHA256 HMAC value of the request body using the HMAC key as the key. The value should match the value in the X-Phabricator-Webhook-Signature field.

To compute the SHA256 HMAC of a string in PHP, do this:

$signature = hash_hmac('sha256', $request_body, $hmac_key);

To compute the SHA256 HMAC of a string in Python, do this:

from subprocess import check_output

signature = check_output(
  [
    "php",
    "-r",
    "echo hash_hmac('sha256', $argv[1], $argv[2]);",
    "--",
    request_body,
    hmac_key
  ])

Other languages often provide similar support.

If you somehow disclose the key by accident, use Regenerate HMAC Key to throw it away and generate a new one.

Request Format

Webhook callbacks are POST requests with a JSON payload in the body. The payload looks like this:

{
  "object": {
    "type": "TASK",
    "phid": "PHID-TASK-abcd..."
  },
  "triggers": [
    {
      "phid": "PHID-HRUL-abcd..."
    }
  ],
  "action": {
    "test": false,
    "silent": false,
    "secure": false,
    "epoch": 12345
  },
  "transactions": [
    {
      "phid": "PHID-XACT-TASK-abcd..."
    }
  ]
}

The object map describes the object which was edited.

The triggers are a list of reasons why the hook was called. When the hook is triggered by Herald rules, the specific rules which triggered the call will be listed. For firehose rules, the rule itself will be listed as the trigger. For test calls, the user making the request will be listed as a trigger.

The action map has metadata about the action:

  • test This was a test call from the web UI or console.
  • silent This is a silent edit which won't send mail or notifications in Phabricator. If your hook is doing something like copying events into a chatroom, it may want to respect this flag.
  • secure Details about this object should only be transmitted over secure channels. Your hook may want to respect this flag.
  • epoch The epoch timestamp when the callback was queued.

The transactions list contains information about the actual changes which triggered the callback.

Responding to Requests

Although trivial hooks may not need any more information than this to act, the information conveyed in the hook body is a minimum set of pointers to relevant data and likely insufficient for more complex hooks.

Complex hooks should expect to react to receiving a request by making API calls to Conduit to retrieve additional information about the object and transactions.

Hooks that are interested in reading object state should generally make a call to a method like maniphest.search or differential.revision.search using the PHID from the object field to retrieve full details about the object state.

Hooks that are interested in changes should generally make a call to transaction.search, passing the transaction PHIDs as a constraint to retrieve details about the transactions.

For example, your call to transaction.search may look something like this:

{
  "objectIdentifier": "PHID-XXXX-abcdef",
  "constraints": {
    "phids": [
      "PHID-XACT-XXXX-11111111",
      "PHID-XACT-XXXX-22222222"
    ]
  }
}

The phid.query method can also be used to retrieve generic information about a list of objects.

Retries and Rate Limiting

Test requests are never retried: they execute exactly once.

Live requests are automatically retried. If your endpoint does not return a HTTP 2XX response, the request will be retried regularly until it suceeds.

Retries will continue until the request succeeds or is garbage collected. By default, this is after 7 days.

If a webhook is disabled, outstanding queued requests will be failed permanently. Activity which occurs while it is disabled will never be sent to the callback URI. (Disabling a hook does not "pause" it so that it can be "resumed" later and pick back up where it left off in the event stream.)

If a webhook encounters a significant number of errors in a short period of time, the webhook will be paused for a few minutes before additional requests are made. The web UI shows a warning indicator when a hook is paused because of errors.

Hook requests time out after 10 seconds. Consider offloading response handling to some kind of worker queue if you expect to routinely require more than 10 seconds to respond to requests.

Hook callbacks are single-threaded: you will never receive more than one simultaneous call to the same webhook from Phabricator. If you have a firehose hook on an active install, it may be important to respond to requests quickly to avoid accumulating a backlog.

Callbacks may be invoked out-of-order. You should not assume that the order you receive requests in is chronological order. If your hook is order-dependent, you can ignore the transactions in the callback and use transaction.search to retrieve a consistent list of ordered changes to the object.

Callbacks may be delayed for an arbitrarily long amount of time, up to the garbage collection limit. You should not assume that calls are real time. If your hook is doing something time-sensitive, you can measure the delivery delay by comparing the current time to the epoch value in the action field and ignoring old actions or handling them in some special way.

Next Steps

Continue by: