Skip to content

Events

During normal operation, DAM emits certain kinds of events that are used internally to perform a range of operations but can also be used externally to build a tight integration. The way these events can be consumed is through web hooks.

In order to receive events, API users first need to subscribe to the event types they want to listen to, and then DAM will forward event information when the events are produced. When API users are no longer interested in receiving events, they can unsubscribe from them by providing the subscription ID that was given at subscription time.

Types of events

All events are sent as POST requests with a JSON body, and the shape of that body depends on the event being notified. Below you can find a list with the events currently supported, the operations that cause each one to be sent, and the payload you can expect from each one.

AfterFileCreated

Sent when one or more new files have finished importing and are available through the DAM API. The operations that lead to this event are:

  • Importing a file from a stream or from a source URL.
  • Promoting a file that was uploaded through the direct upload flow, once DAM detects the completed upload and imports it.
  • Importing files from a ZIP archive, once each file has been extracted and imported.
  • Copying an existing file.

Payload:

1
2
3
4
5
{
    "FileIdsOfCreated": [
        "uuid"
    ]
}

This event is also the readiness signal for the direct upload flow: when you upload a file directly to storage, AfterFileCreated tells you when the file has finished importing. Correlate it by the fileId returned when you requested the upload URL.

Note

Replacing a file does not produce AfterFileCreated, even though a new file is created as part of the replacement. Subscribe to AfterFileReplaced to be notified about replacements.

AfterFileCreateFailed

Sent when a file uploaded directly to blob storage through a pre-signed upload URL cannot be promoted into a DAM file, for both create and replace operations. The failed upload is not retried automatically.

Payload:

1
2
3
4
5
{
    "FileIdsOfFailed": [
        "uuid"
    ]
}

The IDs in FileIdsOfFailed are the same FileIds that were allocated when the uploads were initiated, allowing you to correlate each failure with the originating upload request. These IDs do not correspond to persisted files, so a subsequent GET for one of them returns 404. The payload reports that a file failed to be created, not why; no error reason is included. If you act on AfterFileCreated to handle successful uploads, subscribe to AfterFileCreateFailed to handle the unhappy path.

An upload that never completes, or that completes after the upload URL has expired, does not produce this event. Only uploads that reach DAM and then fail during import do.

AfterFileDeleted

Sent when one or more files have been deleted and are no longer available through the DAM API. The operations that lead to this event are:

  • Deleting one or more files.
  • Deleting a folder, which deletes every file in that folder and its subfolders.

Payload:

1
2
3
4
5
{
    "FileIdsOfDeleted": [
        "uuid"
    ]
}

Note

The file that is superseded by a replacement is deleted as part of the replacement, but does not produce AfterFileDeleted. Its ID is reported in the FromFileIds of the AfterFileReplaced event instead.

AfterFileMasterDataUpdated

Sent when the data of one or more existing files has changed and the change is reflected in the DAM API. This is the event to subscribe to for any metadata change on a file. It is sent both for changes made directly to a file and for changes made elsewhere that affect a file indirectly.

Direct changes:

  • Setting or clearing attribute values on a file, including bulk attribute operations.
  • Changing the master data of a file: file name, file type, description, minimum allowed bit depth, or JPEG export type.
  • Changing the format of a file.
  • Moving one or more files to another folder.
  • Adding or changing a translation on a file.
  • Generating an AI image description for a file.
  • Reducing an image to its recommended size, including when this is requested as part of an import or a replacement.

Indirect changes, where a single operation can affect a large number of files:

  • Updating an attribute definition, which affects every file that uses that attribute.
  • Updating a file type, which affects every file of that type.
  • Renaming a folder, which affects every file in that folder and its subfolders.
  • Updating a content language, which affects every file in DAM.
  • Changing the permissions on a folder, which affects every file in that folder and its subfolders.

Payload:

1
2
3
4
5
{
    "FileIdsOfUpdated": [
        "uuid"
    ]
}

Note

The web hook API also accepts subscriptions to an AfterFileUpdated event type. It is superseded by AfterFileMasterDataUpdated and is never sent, so do not subscribe to it.

AfterFileReplaced

Sent when one or more files have been replaced. It is sent once the replacement has completed in full: the new file has been created, the replaced file has been deleted, and both changes are reflected in the DAM API. The operations that lead to this event are:

  • Replacing a file from a stream or from a source URL.
  • Replacing a file through the direct upload flow, once DAM detects the completed upload and imports it.

Payload:

{
    "FileUpdateDetails": [
        {
            "ToFileId":"uuid",
            "FromFileIds":[
                "uuid",
                "uuid",
                "uuid"
            ],
            "ToFileName":"test.jpg"
        }
    ]
}

ToFileId is the ID of the new file, and FromFileIds holds the IDs of the files it supersedes, including the IDs of files that were replaced in earlier replacements of the same asset. This allows you to redirect references in your own system from any earlier version to the current file.

When events are sent

Events are produced asynchronously. The API call that causes a change returns as soon as the change has been accepted, and the corresponding event is sent afterwards, once DAM has completed the work behind it. Keep the following in mind when building an integration:

  • Events are sent once the change is complete. When you receive an event, the change it describes is already reflected in the DAM API, so you can read the affected files straight away. This is also what determines the delay between the API call and the event: a large operation takes longer to result in an event than a change to a single file.

  • An event covers one or more files, and one operation can produce several events. A single event can carry the IDs of many files, including files affected by separate API calls, and an operation that affects a large number of files is reported through several events as the work completes. Do not assume that one API call results in exactly one event, or that one event corresponds to exactly one file.

  • There are no ordering guarantees. Events for different files, different event types, or different subscriptions can arrive in any order, and a retried event can arrive after later events. Determine the current state from the DAM API rather than relying on the order in which events arrive.

  • An event can be delivered more than once. If your system processes a request but the response does not reach DAM, the request is retried. Make your handlers idempotent.

  • Only events produced after the subscription was created are delivered. Subscribing does not replay events that DAM produced earlier.

Subscribing

There is an endpoint designed to subscribe to events. There is another endpoint to unsubscribe from events. This endpoint requires the subscription ID that was given at subscription time. See the API reference for more information about the web hook endpoints.

Using the subscription endpoint, API users can create a subscription to an event type. The body of the request needs to contain a valid URI for the target. This target represents the endpoint in your system that will be called when the event takes place in DAM. It is not required that the target uses HTTPS.

Generated secret

When subscribing, DAM will generate a symmetric secret for you, which will be returned as a base64 string and not stored or logged. There is no way to retrieve or change this secret afterwards. The secret is used for two purposes:

  • To generate a JWT token that the HTTP call to your system will be authenticated with.
  • To add a signature to the message, so that your systen can check that the contents are correct.

Request signatures

As an additional layer of security, we guarantee that all webhook requests from DAM include a digital signature of their bodies. You can use this signature to ensure that a request is genuine, as well as to protect against replay attacks. We strongly recommend that you always verify the signature on webhook requests.

Verifying request signatures

When DAM sends a webhook request to your system, the request will always contain the following headers:

  • X-Bizzkit-Signature: The request signature as a series of key-value pairs algo1=<signature1>,...,algoN=<signatureN> specifying signature algorithms and base64 encoded signatures respectively.
  • X-Bizzkit-Signature-Timestamp: The UNIX timestamp specifying when the webhook request was initiated by DAM.

The signature header must be treated as a series of comma-separated key-value pairs, despite only a single key-value pair being present for now. This is to support the rotation of algorithms in the future, should the current algorithm become insufficient. Signature algorithms are deprecated in due time before removal. You cannot assume any ordering of algorithms in the signature header, but should instead search for and extract the signatures for those algorithms your customer solution supports.

DAM currently only uses an SHA-256 HMAC signature with base64 encoding to sign its requests. The secret is also treated as base64, whereas the request body and timestamp are treated as UTF-8 encoded strings. Your system needs to reproduce this signature to verify the authenticity of the request. To further provide protection against replay attacks, the signature is calculated from both the request body and timestamp as such:

ToBase64(HMACSHA256(FromBase64(secret), FromUTF8(Timestamp + Body)))

Note

The signature is generated from the raw request body, and not a prettified representation with indentation and newlines, as many frameworks will generate.

Example

Sample signature verification
private static bool VerifySignature(string secret, string body, string xBizzkitSignature, string xBizzkitTimestamp)
{
  var signature = CreateSignature(Convert.FromBase64String(secret),
    Encoding.UTF8.GetBytes(xBizzkitTimestamp + body));
  var expectedBizzkitSignature = $"sha256={signature}";

  // Consider using a constant time comparison to avoid timing attacks
  return expectedBizzkitSignature == xBizzkitSignature;
}

private static string CreateSignature(byte[] key, byte[] payload)
{
  var hash = HashHmacSha256(key, payload);
  return Convert.ToBase64String(hash);
}

private static byte[] HashHmacSha256(byte[] key, byte[] payload)
{
  using var hmac = new HMACSHA256(key);
  return hmac.ComputeHash(payload);
}

Reliability

The following conditions are considered an error when trying to deliver an event:

  • Your system responds with an HTTP status code outside the 2xx range.
  • DAM cannot reach your system.
  • Your system does not respond within 10 seconds.

When a delivery fails, DAM retries it. An event is attempted up to 10 times in total, with a growing delay between attempts, over a period of roughly 20 minutes. If none of the attempts succeed, DAM gives up and the event is discarded.

Retries are handled per subscription, so a failing target does not affect the delivery of the same event to your other subscriptions. If your system can be unavailable for longer than the retry period, reconcile the missed changes through the DAM API rather than relying on the events alone.