Перейти к навигации

Обзор веб-перехватчиков

Пример приложения

Чтобы создать собственный потребитель для веб-перехватчиков Frame.io, можете использовать и расширять наш образец приложения на Github.

Введение

Веб-перехватчики предоставляют способ использования событий, происходящих внутри Frame.io, в качестве уведомлений, которые можно отправить во внешние системы для обработки, обратные вызовы API-интерфейса и автоматизации рабочих процессов.

Настройка

Веб-перехватчики можно настроить в области веб-перехватчиков на сайте для разработчиков. Для веб-перехватчика требуется:

  • Название — будет отображаться только на сайте для разработчиков.
  • URL-адрес — место доставки событий.
  • Команда — к какой команде будет добавлен этот веб-перехватчик.
  • События — какое событие или какие события должны запускать веб-перехватчик.

Поддерживаемые события

Один веб-перехватчик может подписаться на любое количество следующих событий:

Проекты

СобытиеТриггер
project.createdСоздан новый проект
project.updatedОбновлены настройки проекта
project.deletedПроект удален

Ресурсы

СобытиеТриггер
asset.createdРесурс впервые добавляется/создается в Frame.io, но, вероятно, до того, как ресурс будет полностью добавлен
asset.copiedРесурс скопирован
asset.updatedИзменяется описание и название ресурса или другая информация о файле
asset.deletedРесурс удален (вручную или иным способом)
asset.readyВсе транскодирования завершены после добавления и обработки ресурса
asset.label.updatedМетка статуса ресурса устанавливается, изменяется или удаляется
asset.versionedРесурс версионируется
Версионирование ресурса

Когда сработает событие asset.versioned, вы получите полезную нагрузку с идентификатором ресурса, который был версионирован, а не сам стек версий. Поэтому, чтобы передать этот идентификатор в другую функцию, полагая, что это идентификатор стека версий, вам сначала потребуется найти и отследить этот конкретный «родительский» ресурс.

Обновления меток ресурса

Событие asset.label.updated не сработает, когда метка статуса изменится через вызов PUT к конечной точке /v2/assets/:id через общедоступный API-интерфейс (BES-408). Однако оно сработает, когда метка статуса обновится с помощью любых встроенных приложений и интеграций Frame.io (веб-версия, iOS, Premiere, After Effects, FCPX и т. д.).

Комментарии

СобытиеТриггер
comment.createdСоздан новый комментарий или ответ
comment.updatedКомментарий отредактирован
comment.deletedКомментарий удален
comment.completedКомментарий завершен
comment.uncompletedКомментарий не завершен

Ссылки для проверки

СобытиеТриггер
reviewlink.createdСоздана новая ссылка для рецензирования

Соавторы

СобытиеТриггер
collaborator.createdСоавтор добавлен в учетную запись
collaborator.deletedСоавтор удален из учетной записи

Участники команды

СобытиеТриггер
teammember.createdУчастник команды добавлен в учетную запись
teammember.deletedУчастник команды удален из учетной записи

Полезная нагрузка

Frame.io доставляет полезную нагрузку JSON на указанную конечную точку веб-перехватчика. Вот пример полезной нагрузки для события asset.created:

{
"type": "asset.created",
"resource": {
"type": "asset",
"id": "<asset-id>"
},
"user": {
"id": "<user-id>"
},
"team": {
"id": "<team-id>"
}
}

Все полезные нагрузки содержат поле тип, указывающее тип происходящего события, а также объект resource. Объект resource определяет тип и ИД ресурса, связанного с этим событием. В приведенном выше примере события asset.created это будет ИД для только что созданного ресурса. Дополнительно включены объекты пользователь и команда. Они ссылаются на пользователя, который инициировал событие, и контекст команды для ресурса. Помимо непосредственного контекста пользователя и команды, мы не включаем никакой дополнительной информации о подписанном ресурсе. Если вашему приложению требуется дополнительная информация или контекст, рекомендуется использовать наш API-интерфейс HTTP для выполнения последующих запросов.

Повторные попытки

В случае ошибки (ответ с кодом состояния, отличным от 200) или таймаута при доставке веб-перехватчика вашему сервису отправка полезной нагрузки будет повторяться три раза, всего четыре попытки.

Безопасность

По умолчанию все веб-перехватчики предоставляются с ключом подписи. Это не настраивается. Данный ключ можно использовать для проверки того, исходит ли запрос от Frame.io.

Проверка подписей веб-перехватчика

Чтобы защитить интеграцию от атак типа «человек посередине» и атак повторного воспроизведения, важно проверять подписи веб-перехватчика. Проверка гарантирует, что полезная нагрузка веб-перехватчика действительно была отправлена платформой Frame.io, а ее содержимое не было изменено при передаче.

В запрос POST включается следующее:

ИмяОписание
X-Frameio-Request-TimestampВремя доставки веб-перехватчика
X-Frameio-SignatureВычисленная подпись
Метка времени — это время доставки из систем Frame.io. Можно использовать для предотвращения атак повторного воспроизведения. Мы рекомендуем проверять, чтобы это время отличалось от локального времени не более чем на 5 минут. Подпись — это хеш HMAC SHA256, использующий ключ подписи, предоставленный при первоначальном создании веб-перехватчика.

Выполните следующие шаги для проверки подписи.

  1. Извлечение подписи из заголовков HTTP
  2. Создание сообщения для подписи, объединив версию, время доставки и тело запроса в формате: v0:timestamp:body
  3. Вычислите подпись HMAC SHA256 с помощью вашего секретного ключа подписи. Примечание. К предоставленной подписи добавлен префикс v0=. В настоящее время в Frame.io существует только одна версия для подписи запросов. Убедитесь, что этот префикс добавлен к вычисленной вами подписи.
  4. Сравните!
Python
import hmac
import hashlib
def verify_signature(curr_time, req_time, signature, body, secret):
"""
Verify Webhook signature
:Args:
curr_time (float): Current epoch time
req_time (float): Request epoch time
signature (str): Signature provided by the Frame.io API for the given request
body (str): Webhook body from the received POST
secret (str): The secret for this Webhook that you saved when you first created it
"""
if int(curr_time) - int(req_time) < 500:
message = 'v0:{}:{}'.format(req_time, body)
calculated_signature = 'v0={}'.format(hmac.new(
bytes(secret, 'latin-1'),
msg=bytes(message, 'latin-1'),
digestmod=hashlib.sha256).hexdigest())
if calculated_signature == signature:
return True
return False
const crypto = require('crypto');
// Capture the signature, secret, timestamp and payload from a new webhook event:
const
signature = 'v0=a77ce6856e609c884575c2fd211d07a9ad1c3f72e19c06ff710e8f086ffca883',
secret = 'yxSE59T0gtZOFZxw6UhLwTkhd2m8ntNSdSWnApQ0xOnMEzSoXbD8sGFP4bzb7MbS',
timestamp = 1604004499, // UNIX timestamp in seconds
payload = {
"project": {
"id": "f348e9f4-f142-42f9-b3bf-478d93f0feb4"
},
"resource": {
"id": "6aad9151-c216-4d6f-b5e9-530df551a426",
"type": "asset"
},
"team": {
"id": "aa891687-4b1e-4150-9b6d-9e4911c5b436"
},
"type": "asset.label.updated",
"user": {
"id": "59c9ade1-311b-4c3b-8231-b9d88e9a1a85"
}
},
body = JSON.stringify(payload),
// Validate that caught payload is not older than 5 minutes
currentTimeUTC = (new Date()).getTime(),
currentTimestamp = currentTimeUTC / 1000, // JavaScript uses milliseconds whereas Unix Time is in seconds.
minutes = 5,
expired = (currentTimestamp - timestamp) > minutes*60
hmac1 = crypto.createHmac('sha256', secret),
generateSignature = hmac1.update(`v0:${timestamp}:${body}`).digest('hex')
// Evaluates to true if the webhook is verified
console.log(!expired && signature === `v0=${generateSignature}`)
// Full Go sample code: https://github.com/Frameio/webhooks-example-app/blob/master/main.go
func handler(w http.ResponseWriter, r *http.Request) {
out, err := httputil.DumpRequest(r, true)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
return
}
log.Println(string(out))
// Verify the message has been delivered in the last 5 minutes.
timestampStr := r.Header.Get("X-Frameio-Request-Timestamp")
timestamp, err := strconv.ParseInt(timestampStr, 10, 64)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
return
}
if time.Since(time.Unix(timestamp, 0)) > 5*time.Minute {
w.WriteHeader(http.StatusBadRequest)
return
}
// Verify request signature.
expected := r.Header.Get("X-Frameio-Signature")
signature, _ := computeSignature(r, timestamp, secretKey)
if expected != signature {
w.WriteHeader(http.StatusUnauthorized)
return
}
var event *Event
decoder := json.NewDecoder(r.Body)
err = decoder.Decode(&event)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
return
}
// Handle webhook here.
log.Println(event.ID)
w.WriteHeader(http.StatusOK)
}
// The request includes headers to enable the recipient to validate
// that the request is from Frame.io and that it's been delivered within
// the expected time range. To learn more about how this works, take a
// look at our docs https://docs.frame.io/docs/webhooks#section-security.
func computeSignature(r *http.Request, timestamp int64, secret string) (string, error) {
body, err := ioutil.ReadAll(r.Body)
if err != nil {
return "", err
}
copy := body[:]
r.Body = ioutil.NopCloser(bytes.NewReader(copy))
msg := fmt.Sprintf("%s:%d:%s", version, timestamp, string(body))
key := []byte(secret)
h := hmac.New(sha256.New, key)
h.Write([]byte(msg))
result := fmt.Sprintf("%s=%s", version, hex.EncodeToString(h.Sum(nil)))
return result, nil
}