Developer console

Verifying Webhooks

Verify webhook signatures to confirm that received events are sent from Original. Original signs webhook events it sends to your endpoints by including a signature in each event’s x-webhook-signature header. This allows you to verify that the events were sent by Original, not by a third party.

Original signatures is a HMAC of the received event payload, using SHA-256 hash function and the webhook secret as the secret. Each signature is prefixed by the key and a comma associated with the secret used to sign the payload. Each key,signature pair is space delimited. It has this shape:

{
  'x-webhook-signature': '4o3vfxtcmo7b,d72c5...39d84d1b974ca ws7orr8kbho6,fae80448...033bb821c9'
}

Representing:

{
  'x-webhook-signature': 'key1,signature1 key2,signature2 key3,signature3'
}

If you have multiple keys, we will include all of the signatures for all the keys present in your keys section for your webhook. This is so you can rotate keys safely. You can find your secrets and associated keys in the Keys section of the Webhooks page:

Verifying Webhooks

Given a sample request body:

{
  "data": {
    "app": {
      "uid": "UB2DG8MW5KBQ",
      "name": "App name567",
      "region": "EU",
      "environment": "Development"
    },
    "asset": {
      "uid": "xxxxxxxxxxxx",
      "name": "Dave Starbelly",
      "metadata": {
        "name": "Dave Starbelly",
        "image": "https://storage.googleapis.com/opensea-prod.appspot.com/puffs/3.png",
        "original_id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
        "org_image_url": "https://storage.googleapis.com/opensea-prod.appspot.com/puffs/3.png",
        "attributes": [
          {
            "trait_type": "Base",
            "value": "Starfish"
          }
        ]
      },
    },
  },
  "event": "asset.minted",
  "event_time": "2023-08-01T11:24:36.531141+00:00",
  "sent_at": "2023-08-01T11:24:36.531141+00:00"
}

And sample headers:

{
	'x-webhook-signature':	'4o3vfxtcmo7b,d72c56b2a699be98806b45ac968b6d5c2c5072addd67e18eda739d84d1b974ca'
	'x-app-uid':	'UB2DG8MW5KBQ'
	'x-webhook-correlation-id':	'44a5f8c9-86f6-40c4-97b7-e0456caf928a'
	'x-webhook-attempt': '1'
}

Here is a sample of how you can verify webhooks in NodeJS


const crypto = require('crypto')

const WEBHOOK_KEY = process.env.WEBHOOK_KEY
const WEBHOOK_SECRET = process.env.WEBHOOK_SECRET // the webhook secret you got from the Keys page

function handleWebhook(req, res) {
  console.log('Received event:', req.body, req.headers['x-webhook-signature']);
  
  const eventSignature = getEventSignature(req);
  const isSignatureValid = verifyOriginalWebhookSignature(req.body, eventSignature);
  
  if (isSignatureValid) {
    // process event ...
  }
}

function getEventSignature(req) {
  const signatures =  req.headers['x-webhook-signature'].split(' ');
  
  const signatureWithKey = signatures.find(signature => signature
    .startsWith(`${WEBHOOK_KEY},`));

  const eventSignature = signatureWithKey.split(',')[1];
  
  return eventSignature;
}

function verifyOriginalWebhookSignature(eventPayload, eventSignature) {
  const messageToSign = JSON.stringify(eventPayload)

  const signature = crypto
    .createHmac('sha256', WEBHOOK_SECRET)
    .update(messageToSign)
    .digest('hex')

  const trustedSig = Buffer.from(signature, 'ascii')
  const untrustedSig = Buffer.from(eventSignature, 'ascii')

  const isSignatureValid = crypto.timingSafeEqual(trustedSig, untrustedSig) // using a constant-time equality comparison (to avoid timing attacks)

  if (isSignatureValid) {
    // Optionally check the event time
    // eventPayload.event_time = xxx
  }

  return isSignatureValid
}

And a sample to verify in Python:

import hashlib
import hmac
import json


def verify_signature(event_payload: dict, event_signature: str) -> bool:
    payload = json.dumps(event_payload, separators=(",", ":"))

    trusted_sig = hmac.new(
        WEBHOOK_SECRET.encode("utf-8"),
        msg=payload.encode("utf-8"),
        digestmod=hashlib.sha256,
    ).hexdigest()

    untrusted_sig = event_signature

    is_signature_valid = hmac.compare_digest(trusted_sig, untrusted_sig)

    return is_signature_valid