Información general sobre los webhooks

Aplicación de ejemplo

Si desea crear su propio consumidor para los webhooks de Frame.io, puede descargar y ampliar nuestra aplicación de ejemplo en GitHub.

Introducción

Los webhooks proporcionan una forma de aprovechar eventos que ocurren dentro de Frame.io en notificaciones que se pueden enviar a sistemas externos para el procesamiento, las devoluciones de llamadas API y, en última instancia, la automatización de flujos de trabajo.

Configurar

Los webhooks pueden configurarse en el área de webhooks de nuestro sitio para desarrolladores. Un webhook requiere:

  • Nombre: Se mostrará únicamente en el sitio para desarrolladores.
  • URL: Lugar donde se entregarán los eventos.
  • Equipo: A qué equipo se añadirá este webhook.
  • Eventos: Qué evento o eventos deben activar el webhook.

Eventos compatibles

Un solo webhook puede suscribirse a todos los siguientes eventos:

Proyectos

EventoActivador
project.createdSe ha creado un nuevo proyecto
project.updatedSe ha actualizado la configuración de un proyecto
project.deletedSe ha eliminado un proyecto

Activos

EventoActivador
asset.createdUn activo se añade/crea por primera vez en Frame.io, pero probablemente antes de que el activo se cargue completamente
asset.copiedSe ha copiado un activo
asset.updatedSe ha cambiado la descripción, el nombre u otra información de archivo de un activo
asset.deletedSe ha eliminado un activo (manualmente o de otra forma)
asset.readyTodas las transcodificaciones han finalizado después de que un archivo se haya cargado y procesado
asset.label.updatedLa etiqueta de estado de un activo se ha establecido, cambiado o quitado
asset.versionedUn activo tiene versiones
Versiones de activos

Cuando se activa el evento asset.versioned, va a recibir una carga útil con el id del activo que tiene versiones, no del grupo de versiones en sí. Por lo tanto, si espera poder pasar ese id a otra función pensando que es el id del grupo de versiones, va a tener que buscar y localizar primero ese recurso “principal” en concreto.

Actualizaciones de etiquetas de activos

El evento asset.label.updated no se activará cuando la etiqueta de estado se cambie mediante una llamada PUT al punto final /v2/assets/:id a través de la API pública (BES-408). Sin embargo, se activará cuando la etiqueta de estado se actualice mediante cualquier aplicación e integración nativa de Frame.io (Web, iOS, Premiere, After Effects, FCPX, etc.).

Comentarios

EventoActivador
comment.createdSe ha creado un nuevo comentario o una nueva respuesta
comment.updatedSe ha editado un comentario
comment.deletedSe ha eliminado un comentario
comment.completedSe ha completado un comentario
comment.uncompletedSe ha marcado un comentario como no completado

Vínculos de revisión

EventoActivador
reviewlink.createdSe ha creado un vínculo de revisión nuevo

Colaboradores

EventoActivador
collaborator.createdSe ha añadido un colaborador a su cuenta
collaborator.deletedSe ha quitado un colaborador de su cuenta

Integrantes del equipo

EventoActivador
teammember.createdSe ha añadido un integrante del equipo a su cuenta
teammember.deletedSe ha quitado un integrante del equipo de su cuenta

Carga útil

Frame.io entrega una carga útil JSON al punto final del webhook especificado. Este es un ejemplo de carga útil para un evento asset.created:

1{
2 "type": "asset.created",
3 "resource": {
4 "type": "asset",
5 "id": "<asset-id>"
6 },
7 "user": {
8 "id": "<user-id>"
9 },
10 "team": {
11 "id": "<team-id>"
12 }
13}

Todas las cargas útiles contienen un campo type, que indica el tipo de evento que se produce, así como un objeto resource. El objeto resource especifica el type y el id del recurso relacionado con el evento. En el ejemplo anterior de un evento asset.created, este sería el id del nuevo activo creado. Además, se incluyen objetos user y team. Estos hacen referencia al usuario que activó el evento y al contexto del equipo para el recurso. Aparte del contexto inmediato de usuario y equipo, no incluimos información adicional sobre el recurso suscrito. Si la aplicación requiere información o contexto adicional, recomendamos usar nuestra API HTTP para realizar solicitudes de seguimiento.

Reintentos

Si se produce un error (respuesta con código de estado distinto de 200) o se agota el tiempo de espera al entregar el webhook a su servicio, la carga útil se reintentará tres veces, para un total de cuatro intentos de entrega.

Seguridad

De forma predeterminada, todos los webhooks se proporcionan con una clave de firma. Esta clave no se puede configurar.Puede usarse para verificar que la solicitud procede de Frame.io.

Verificar firmas de webhook

Para proteger una integración contra ataques de intermediario y de repetición, es esencial verificar las firmas de webhook. La verificación garantiza que las cargas útiles de webhook las haya enviado realmente Frame.io y que el contenido no se haya modificado durante el transporte.

La solicitud POST incluye los siguientes encabezados:

NombreDescripción
X-Frameio-Request-TimestampLa marca de tiempo de la entrega del webhook
X-Frameio-SignatureLa firma calculada
La marca de tiempo es el momento de la entrega desde los sistemas de Frame.io. Puede usarse para evitar ataques de reproducción.Recomendamos verificar que esta hora esté dentro de un intervalo de 5 minutos con respecto a la hora local.La firma es un hash HMAC SHA-256 que usa la clave de firma proporcionada al crear el webhook por primera vez.

Siga estos pasos para verificar la firma:

  1. Extraiga la firma de los encabezados HTTP
  2. Cree un mensaje para firmarlo combinando la versión, la hora de entrega y el cuerpo de la solicitud: v0:timestamp:body
  3. Calcule la firma HMAC SHA-256 con el secreto de firma.Nota: La firma proporcionada lleva el prefijo v0=. Actualmente, Frame.io solo tiene esta versión para firmar solicitudes.Asegúrese de añadir este prefijo al principio de la firma calculada.
  4. Haga una comparación.
Python
1import hmac
2import hashlib
3
4def verify_signature(curr_time, req_time, signature, body, secret):
5 """
6 Verify Webhook signature
7 :Args:
8 curr_time (float): Current epoch time
9 req_time (float): Request epoch time
10 signature (str): Signature provided by the Frame.io API for the given request
11 body (str): Webhook body from the received POST
12 secret (str): The secret for this Webhook that you saved when you first created it
13 """
14 if int(curr_time) - int(req_time) < 500:
15 message = 'v0:{}:{}'.format(req_time, body)
16 calculated_signature = 'v0={}'.format(hmac.new(
17 bytes(secret, 'latin-1'),
18 msg=bytes(message, 'latin-1'),
19 digestmod=hashlib.sha256).hexdigest())
20 if calculated_signature == signature:
21 return True
22 return False
1const crypto = require('crypto');
2
3// Capture the signature, secret, timestamp and payload from a new webhook event:
4const
5signature = 'v0=a77ce6856e609c884575c2fd211d07a9ad1c3f72e19c06ff710e8f086ffca883',
6secret = 'yxSE59T0gtZOFZxw6UhLwTkhd2m8ntNSdSWnApQ0xOnMEzSoXbD8sGFP4bzb7MbS',
7timestamp = 1604004499, // UNIX timestamp in seconds
8payload = {
9 "project": {
10 "id": "f348e9f4-f142-42f9-b3bf-478d93f0feb4"
11 },
12 "resource": {
13 "id": "6aad9151-c216-4d6f-b5e9-530df551a426",
14 "type": "asset"
15 },
16 "team": {
17 "id": "aa891687-4b1e-4150-9b6d-9e4911c5b436"
18 },
19 "type": "asset.label.updated",
20 "user": {
21 "id": "59c9ade1-311b-4c3b-8231-b9d88e9a1a85"
22 }
23},
24body = JSON.stringify(payload),
25
26// Validate that caught payload is not older than 5 minutes
27currentTimeUTC = (new Date()).getTime(),
28currentTimestamp = currentTimeUTC / 1000, // JavaScript uses milliseconds whereas Unix Time is in seconds.
29minutes = 5,
30expired = (currentTimestamp - timestamp) > minutes*60
31hmac1 = crypto.createHmac('sha256', secret),
32generateSignature = hmac1.update(`v0:${timestamp}:${body}`).digest('hex')
33
34// Evaluates to true if the webhook is verified
35console.log(!expired && signature === `v0=${generateSignature}`)
1// Full Go sample code: https://github.com/Frameio/webhooks-example-app/blob/master/main.go
2
3func handler(w http.ResponseWriter, r *http.Request) {
4 out, err := httputil.DumpRequest(r, true)
5 if err != nil {
6 w.WriteHeader(http.StatusInternalServerError)
7 return
8 }
9
10 log.Println(string(out))
11
12 // Verify the message has been delivered in the last 5 minutes.
13 timestampStr := r.Header.Get("X-Frameio-Request-Timestamp")
14 timestamp, err := strconv.ParseInt(timestampStr, 10, 64)
15 if err != nil {
16 w.WriteHeader(http.StatusBadRequest)
17 return
18 }
19
20 if time.Since(time.Unix(timestamp, 0)) > 5*time.Minute {
21 w.WriteHeader(http.StatusBadRequest)
22 return
23 }
24
25 // Verify request signature.
26 expected := r.Header.Get("X-Frameio-Signature")
27 signature, _ := computeSignature(r, timestamp, secretKey)
28 if expected != signature {
29 w.WriteHeader(http.StatusUnauthorized)
30 return
31 }
32
33 var event *Event
34 decoder := json.NewDecoder(r.Body)
35 err = decoder.Decode(&event)
36 if err != nil {
37 w.WriteHeader(http.StatusInternalServerError)
38 return
39 }
40
41 // Handle webhook here.
42 log.Println(event.ID)
43
44 w.WriteHeader(http.StatusOK)
45}
46
47// The request includes headers to enable the recipient to validate
48// that the request is from Frame.io and that it's been delivered within
49// the expected time range. To learn more about how this works, take a
50// look at our docs https://docs.frame.io/docs/webhooks#section-security.
51func computeSignature(r *http.Request, timestamp int64, secret string) (string, error) {
52 body, err := ioutil.ReadAll(r.Body)
53 if err != nil {
54 return "", err
55 }
56 copy := body[:]
57 r.Body = ioutil.NopCloser(bytes.NewReader(copy))
58
59 msg := fmt.Sprintf("%s:%d:%s", version, timestamp, string(body))
60
61 key := []byte(secret)
62 h := hmac.New(sha256.New, key)
63 h.Write([]byte(msg))
64
65 result := fmt.Sprintf("%s=%s", version, hex.EncodeToString(h.Sum(nil)))
66
67 return result, nil
68}