1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-11-22 14:52:41 +01:00

Document how webhooks work

Summary: Depends on D19049. Ref T11330. Adds some documentation for webhooks.

Test Plan: Read the documentation and found it to be exceptionally accurate and helpful.

Maniphest Tasks: T11330

Differential Revision: https://secure.phabricator.com/D19050
This commit is contained in:
epriestley 2018-02-09 12:59:55 -08:00
parent 98c701ffc5
commit 64177cb16e
5 changed files with 219 additions and 1 deletions

View file

@ -28,6 +28,10 @@ final class PhabricatorHeraldApplication extends PhabricatorApplication {
'name' => pht('Herald User Guide'),
'href' => PhabricatorEnv::getDoclink('Herald User Guide'),
),
array(
'name' => pht('User Guide: Webhooks'),
'href' => PhabricatorEnv::getDoclink('User Guide: Webhooks'),
),
);
}

View file

@ -155,6 +155,7 @@ final class HeraldWebhookWorker
'test' => $request->getIsTestAction(),
'silent' => $request->getIsSilentAction(),
'secure' => $request->getIsSecureAction(),
'epoch' => (int)$request->getDateCreated(),
),
'transactions' => $xaction_data,
);

View file

@ -377,7 +377,7 @@ abstract class PhabricatorBulkEngine extends Phobject {
'')))
->appendChild(
id(new AphrontFormSubmitControl())
->setValue(pht('Apply Bulk Edit'))
->setValue(pht('Continue'))
->addCancelButton($cancel_uri));
}

View file

@ -4290,6 +4290,7 @@ abstract class PhabricatorApplicationTransactionEditor
->setObjectPHID($object->getPHID())
->setTransactionPHIDs(mpull($xactions, 'getPHID'))
->setTriggerPHIDs($trigger_phids)
->setRetryMode(HeraldWebhookRequest::RETRY_FOREVER)
->setIsSilentAction((bool)$this->getIsSilent())
->setIsSecureAction((bool)$this->getMustEncrypt())
->save();

View file

@ -0,0 +1,212 @@
@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
```
You can use a tool like [[ https://requestb.in | RequestBin ]] to inspect
the headers and payload for calls to hooks.
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.
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}.