You may wish to create endpoints to receive asynchronous events from your Prequel integration, such as transfer failures. Webhook endpoints created using the POST /webhooks endpoint can subscribe to any of Prequel’s Event Types and configure delivery via HTTPS, Slack, or Pagerduty.

Delivery Methods

HTTP POST and GET

Prequel supports HTTPS callbacks to your custom webhook receiver. Creating a webhook endpoint as the generic_post type will cause payloads to be sent as a JSON payload. The generic_get type will deliver payloads as URL parameters. The payloads are defined in Event Types.

PagerDuty & Slack

You may wish to have events sent to specific vendors for special handling of webhook requests. Currently, PagerDuty and Slack are supported, with specific payload shapes according to the vendor specifications.

Authentication

You can provide an API key for the webhook to use if the destination requires one. Additionally, Prequel signs every payload and passes the signature through the X-Prequel-Webhook-Signature header. See Verifying Webhooks for more information on handling the signature.

Versioning

Prequel uses a top-level “version” field. A version is represented by the date it was released. Currently, all webhooks use the version 2023-10-15.

Contents & Structure

All event types follow this structure:

Headers

HeaderDescription
Content-TypeAlways application/json
X-Prequel-Webhook-TimestampTimestamp generated when the event is sent.
X-Prequel-Webhook-SignatureSignature generated by Prequel using SHA-256 and RSA PKCS1 v1.5 signature scheme. See Verifying Webhooks.
X-Prequel-Webhook-DigestOptional utility for signature verification. See Verifying Webhooks.

Body

{
  “type”: “resource.event”,
  “version”: “XXXX-XX-XX”,
  “created_at”: …, // timestamp generated when the event is created
  “data”: {
    // event specific
  }
}

Event Types

When creating a webhook, you must specify which events it should listen to. Webhooks created before October 31 2023 will continue to receive only transfer errors.

Transfer errors

This event has type transfer.error. It is sent whenever Prequel identifies a partial or full transfer failure. Note the following fields which do not appear on the transfer object itself:

  • transfer_error_type: May be any of first_party, third_party, prequel , host. Defaults to prequel. first_party indicates an error with your Prequel integration. third_party indicates to an error caused by your customer; for example, a change in existing credentials for a destination. host is only applicable if you are self-hosting Prequel.
  • transfer_log: Full log of the error.
  • transfer_log_pretty: If transfer_error_type is third_party, a modified error string suitable for displaying to customers. Otherwise empty.

Example transfer error:

{
  "type": "transfer.error",
  "api_version": "2023-10-15",
  "created_at": "2023-10-15T19:19:08+00:00",
  "data": {
    "error": {
      "destination_id": "00000000-0000-0000-0000-000000000000",
      "destination_name": "Foo Bar",
      "id_in_provider_system": "acme",
      "models": ["users"],
      "is_full_refresh": true,
      "transfer_id": "00000000-0000-0000-0000-000000000000",
      "transfer_status": "PARTIAL_ERROR",
      "transfer_error_type": "first_party_error",
      "transfer_log": "Exception in thread \"main\" org.apache.spark.SparkException: Job aborted due to stage failure: Task 3 in stage 4.0 failed 4 times, most recent failure: Lost task 3.3 in stage 4.0 (TID 132, executor 2): java.lang.NullPointerException at org.apache.spark.sql.execution.datasources.FileScanRDD$$anon$1.next(FileScanRDD.scala)",
      "transfer_log_pretty": "Error due to the heat death of the universe",
      "transfer_started_at": "2023-10-15T16:19:08+00:00",
      "transfer_ended_at": "2023-10-15T18:19:08+00:00",
      "in_app_url": "https://app.prequel.co/export/destinations/00000000-0000-0000-0000-000000000000/transfers"
    }
  }
}

Other events

Aside from transfer errors, events are of the type resource.created, resource.updated or resource.deleted. The resources that currently generate webhooks are sources, destinations, recipients, providers, and magic_links.

Example event:

{
  "type": "recipient.updated",
  "api_version": "2023-10-15",
  "created_at": "2023-10-15T19:19:08+00:00",
  "data": {
    "current_resource": {
      "id": "00000000-0000-0000-0000-000000000000",
      "name": "Acme Inc",
      "products": ["billing", "invoices"],
      "created_at": "2023-10-15T19:19:08+00:00",
    },
    "previous_resource": {
      "id": "00000000-0000-0000-0000-000000000000",
      "name": "Acme Inc",
      "products": ["billing"],
      "created_at": "2023-10-15T19:19:08+00:00",
    }
  }
}

Verifying Webhooks

Prequel provides a unique signature in the HTTP header of each webhook request. You can use this signature and your account’s webhook public key to verify that the data you receive is from Prequel.

Webhook Signatures

Prequel’s approach to webhook signatures is based on asymmetric cryptography. Prequel generates an RSA private and public key pair for your account’s webhook integration. After generating a webhook, Prequel digitally signs the payload with the private key. On receiving the webhook, you can use the public key to verify the authenticity and freshness of this signature. See Verifying The Signature.

Relevant headers

HeaderDescription
X-Prequel-Webhook-TimestampTimestamp the webhook was sent, in RFC 3339 format.
X-Prequel-Webhook-SignatureSignature generated by Prequel using the [signing data]
(Maybe) X-Prequel-Webhook-DigestSHA-256 hash of the body provided by Prequel for debugging purposes

Verify the signature

The general steps to verify a signature are outlined below.

1. Retrieve your webhook key

You can retrieve your webhook key by hitting the following API endpoint /public/signatures/webhhook-public-key. See the API reference.

2. Reconstruct the signing data

Extract the timestamp provided in the X-Prequel-Webhook-Timestamp header. The date is in RFC 3339 format and looks something like:
X-Prequel-Webhook-Timestamp: 2023-10-15T14:30:00Z

The signing data is in the format timestamp.body, constructed by concatenating

  • The timestamp
  • The character .
  • The request body, i.e. the raw JSON payload

Finally, hash the full signing data using SHA-256.

🚧

Warning

Prequel uses the raw body of the request to generate a signature. You should hash the raw body string (or bytes) before deserializing the JSON payload. Frameworks that parse the response body may introduce subtle changes, such as removing characters or changing key-sort order.

Validate the response hash

Prequel hashes the raw body only and provides the result in the X-Prequel-Webhook-Digest header. You can use this to validate that you are handling the response body correctly. You should not use this to confirm the signature.

3. Confirm the signature

Once you’ve reconstructed and hashed the signing data, you can sign the data with your public key and compare the output to the signature in the X-Prequel-Webhook-Signature header.

Prequel uses the PKCS1 v1.5 signature scheme.

4. Verify timing

A replay attack is when an attacker intercepts a valid payload and its signature, then re-transmits them. The provided timestamp is verified by the signature, so an attacker can’t change the timestamp without invalidating the signature. However, even if the attacker cannot alter the data, they may produce side-effects from processing the same event multiple times. We recommend defining a time window (such as 5 minutes) and rejecting events that are too old.