Since async predict results are sent to a webhook available to anyone over the internet with the endpoint, you’ll want to have some verification that these results sent to the webhook are actually coming from Baseten.

We recommend leveraging webhook signatures to secure webhook payloads and ensure they are from Baseten.

This is a two-step process:

  1. Create a webhook secret.
  2. Validate a webhook signature sent as a header along with the webhook request payload.

Creating webhook secrets

Webhook secrets can be generated via the Secrets tab.

Generate a webhook secret with the "Add webhook secret" button.

A webhook secret looks like:

whsec_AbCdEf123456GhIjKlMnOpQrStUvWxYz12345678

Ensure this webhook secret is saved securely. It can be viewed at any time and rotated if necessary in the Secrets tab.

Validating webhook signatures

If a webhook secret exists, Baseten will include a webhook signature in the "X-BASETEN-SIGNATURE" header of the webhook request so you can verify that it is coming from Baseten.

A Baseten signature header looks like:

"X-BASETEN-SIGNATURE": "v1=signature"

Where signature is an HMAC generated using a SHA-256 hash function calculated over the whole async predict result and signed using a webhook secret.

If multiple webhook secrets are active, a signature will be generated using each webhook secret. In the example below, the newer webhook secret was used to create newsignature and the older (soon to expire) webhook secret was used to create oldsignature.

"X-BASETEN-SIGNATURE": "v1=newsignature,v1=oldsignature"

To validate a Baseten signature, we recommend the following. A full Baseten signature validation example can be found in this Repl.

1

Compare timestamps

Compare the async predict result timestamp with the current time and decide if it was received within an acceptable tolerance window.

TIMESTAMP_TOLERANCE_SECONDS = 300

# Check timestamp in async predict result against current time to ensure its within our tolerance
if (datetime.now(timezone.utc) -
    async_predict_result.time).total_seconds() > TIMESTAMP_TOLERANCE_SECONDS:
  logging.error(
      f"Async predict result was received after {TIMESTAMP_TOLERANCE_SECONDS} seconds and is considered stale, Baseten signature was not validated."
  )
2

Recompute Baseten signature

Recreate the Baseten signature using webhook secret(s) and the async predict result.

WEBHOOK_SECRETS = [] # Add your webhook secrets here

async_predict_result_json = async_predict_result.model_dump_json()

# We recompute expected Baseten signatures with each webhook secret
for webhook_secret in WEBHOOK_SECRETS:
  for actual_signature in baseten_signature.replace("v1=", "").split(","):
    expected_signature = hmac.digest(
        webhook_secret.encode("utf-8"),
        async_predict_result_json.encode("utf-8"),
        hashlib.sha256,
    ).hex()
3

Compare signatures

Compare the expected Baseten signature with the actual computed signature using compare_digest, which will return a boolean representing whether the signatures are indeed the same.

hmac.compare_digest(expected_signature, actual_signature)

Keeping webhook secrets secure

We recommend periodically rotating webhook secrets.

In the event that a webhook secret is exposed, you’re able to rotate or remove it.

Rotating a secret in the UI will set the existing webhook secret to expire in 24 hours, and generate a new webhook secret. During this period, Baseten will include multiple signatures in the signature headers.

Removing webhook secrets could cause your signature validation to fail. Recreate a webhook secret after deleting and ensure your signature validation code is up to date with the new webhook secret.