@title User Guide: Webhooks @group userguide 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 {nav Herald > Webhooks}. 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 {nav 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 {nav 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: ```lang=php $signature = hash_hmac('sha256', $request_body, $hmac_key); ``` To compute the SHA256 HMAC of a string in Python, do this: ```lang=python 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 {nav 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: ```lang=json { "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: ```lang=json { "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: - learning more about Herald with @{article:Herald User Guide}; or - interacting with the Conduit API with @{article:Conduit API Overview}.