Visão geral dos webhooks

Aplicativo de exemplo

Se quiser criar seu próprio consumidor para webhooks do Frame.io, fique à vontade para usar e estender nosso aplicativo de exemplo no GitHub.

Introdução

Os webhooks oferecem uma maneira de aproveitar eventos que ocorrem dentro do Frame.io em notificações que podem ser enviadas para sistemas externos para processamento, callbacks de API e, por fim, automação do fluxo de trabalho.

Configuração

Os webhooks podem ser configurados na área de webhooks do nosso site para desenvolvedores.Um webhook requer:

  • Nome — será exibido apenas no site para desenvolvedores.
  • URL — onde entregar eventos.
  • Equipe — a qual equipe este webhook será adicionado.
  • Eventos — qual evento, ou eventos, devem acionar o webhook.

Eventos compatíveis

Um único webhook pode se inscrever para receber qualquer número dos seguintes eventos:

Projetos

EventoAcionador
project.createdUm novo projeto é criado
project.updatedAs configurações de um projeto são atualizadas
project.deletedUm projeto é excluído

Ativos

EventoAcionador
asset.createdUm recurso é adicionado/criado inicialmente no Frame.io, mas provavelmente antes de ter sido totalmente carregado
asset.copiedUm ativo foi copiado
asset.updatedA descrição, nome ou outras informações de arquivo de um ativo são alteradas
asset.deletedUm ativo é excluído (manualmente ou de outra forma)
asset.readyTodas as transcodificações foram concluídas após um ativo ter sido carregado e processado
asset.label.updatedO rótulo de status de um ativo é definido, alterado ou removido
asset.versionedUm ativo tem controle de versão
Controle de versão de ativo

Quando o evento asset.versioned é disparado, você recebe um conteúdo com o ID do ativo que teve controle de versão, não a própria pilha de versões.Portanto, se você espera conseguir passar esse id para outra função pensando que é o id da pilha de versões, você vai ter que procurar e rastrear esse recurso ‘principal’ em particular primeiro.

Atualizações de rótulo de ativo

O evento asset.label.updated não será disparado quando o rótulo de status for alterado por meio de uma chamada PUT para o ponto de acesso /v2/assets/:id via API pública (BES-408).No entanto, ele será disparado quando o rótulo de status for atualizado usando qualquer aplicativo nativo e integrações do Frame.io (Web, iOS, Premiere, After Effects, FCPX, etc).

Comentários

EventoAcionador
comment.createdUm novo comentário ou resposta é criado
comment.updatedUm comentário é editado
comment.deletedUm comentário é excluído
comment.completedUm comentário é concluído
comment.uncompletedUm comentário é marcado como não concluído
EventoAcionador
reviewlink.createdUm novo link de revisão é criado

Colaboradores

EventoAcionador
collaborator.createdUm usuário colaborador foi adicionado à sua conta
collaborator.deletedUm usuário colaborador foi removido da sua conta

Membros da equipe

EventoAcionador
teammember.createdUm membro da equipe foi adicionado à sua conta
teammember.deletedUm membro da equipe foi removido da sua conta

Conteúdo

O Frame.io entrega um conteúdo JSON para o ponto de acesso do webhook especificado.Este é um exemplo de conteúdo para um 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}

Todos os conteúdos contêm um campo type, indicando o tipo de evento que está ocorrendo, bem como um objeto resource.O objeto resource especifica o type e o id do recurso relacionado a este evento.No exemplo acima de um evento asset.created, este seria o id para o ativo recém-criado.Além disso, os objetos user e team estão incluídos.Estes fazem referência ao usuário que acionou o evento e ao contexto da equipe para o recurso.Além do contexto imediato do usuário e da equipe, não incluímos informações adicionais sobre o recurso assinado.Se o aplicativo precisar de informações ou contexto adicionais, recomendamos usar nossa API HTTP para fazer solicitações de acompanhamento.

Novas tentativas

Se ocorrer um erro (resposta de código de status diferente de 200) ou tempo-limite durante a entrega do webhook para seu serviço, o conteúdo será tentado novamente três vezes, totalizando quatro tentativas de entrega.

Segurança

Por padrão, todos os webhooks são fornecidos com uma chave de assinatura.Isso não é configurável.Essa chave pode ser usada para verificar se a solicitação se origina do Frame.io.

Verificar assinaturas de webhook

Para proteger uma integração contra ataques “man-in-the-middle” e de repetição, é essencial verificar as assinaturas de webhooks.A verificação garante que os conteúdos de webhook foram realmente enviados pelo Frame.io e que o conteúdo não foi modificado durante o transporte.

Incluídos na solicitação POST estão os seguintes cabeçalhos:

NomeDescrição
X-Frameio-Request-TimestampA hora da entrega do webhook
X-Frameio-SignatureA assinatura computada
O carimbo de data e hora é a hora da entrega dos sistemas do Frame.io.Isso pode ser usado para impedir ataques de repetição.Recomendamos verificar se essa hora está dentro de um intervalo de 5 minutos em relação à hora local.A assinatura é um hash HMAC SHA256 usando a chave de assinatura fornecida quando o webhook é criado pela primeira vez.

Siga estas etapas para verificar a assinatura:

  1. Extraia a assinatura dos cabeçalhos HTTP
  2. Crie uma mensagem para assinar combinando a versão, a hora da entrega e o corpo da solicitação: v0:timestamp:body
  3. Compute a assinatura HMAC SHA-256 utilizando seu segredo de assinatura.Observação: A assinatura fornecida tem o prefixo v0=.Atualmente, o Frame.io possui apenas essa versão para assinar solicitações.Certifique-se de que este prefixo seja anexado à sua assinatura computada.
  4. Compare!
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}