@trillboards/api-client 1.0.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chunk-IPFVLAUW.mjs +18 -0
- package/dist/chunk-IPFVLAUW.mjs.map +1 -0
- package/dist/generated.d.mts +174 -38
- package/dist/generated.d.ts +174 -38
- package/dist/generated.js +4 -4
- package/dist/generated.js.map +1 -1
- package/dist/generated.mjs +1 -1
- package/dist/index.d.mts +65 -2
- package/dist/index.d.ts +65 -2
- package/dist/index.js +177 -6
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +167 -4
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-DU4JFJJQ.mjs +0 -11
- package/dist/chunk-DU4JFJJQ.mjs.map +0 -1
package/dist/generated.mjs
CHANGED
package/dist/index.d.mts
CHANGED
|
@@ -821,17 +821,80 @@ declare function constructEvent(payload: string | Buffer, headers: WebhookHeader
|
|
|
821
821
|
* legacy shim preserves only for migration compatibility.
|
|
822
822
|
*/
|
|
823
823
|
declare function constructEventLegacy(payload: string | Buffer, signature: string, secret: string): WebhookEvent;
|
|
824
|
+
/** RFC 9421 `tag` Trillboards stamps on outbound AdCP webhooks. */
|
|
825
|
+
declare const ADCP_WEBHOOK_SIGNING_TAG = "adcp/webhook-signing/v1";
|
|
826
|
+
/** An Ed25519 public key from the Trillboards JWKS. */
|
|
827
|
+
interface AdcpJwk {
|
|
828
|
+
kty: string;
|
|
829
|
+
crv: string;
|
|
830
|
+
x: string;
|
|
831
|
+
kid: string;
|
|
832
|
+
alg?: string;
|
|
833
|
+
use?: string;
|
|
834
|
+
key_ops?: string[];
|
|
835
|
+
adcp_use?: string;
|
|
836
|
+
}
|
|
837
|
+
interface VerifyAdcpWebhookOptions {
|
|
838
|
+
/** Absolute URL the webhook was delivered to. Must be the URL you published. */
|
|
839
|
+
url: string;
|
|
840
|
+
/** HTTP method of the delivery. Defaults to POST. */
|
|
841
|
+
method?: string;
|
|
842
|
+
/** Delivery headers (case-insensitive). */
|
|
843
|
+
headers: WebhookHeaders;
|
|
844
|
+
/** Raw request body — EXACTLY the bytes received, never a re-stringify. */
|
|
845
|
+
payload: string | Buffer;
|
|
846
|
+
/** Keys from https://api.trillboards.com/.well-known/jwks.json */
|
|
847
|
+
keys: AdcpJwk[];
|
|
848
|
+
/** Override the verifier's clock (seconds). Tests only. */
|
|
849
|
+
nowSec?: number;
|
|
850
|
+
}
|
|
851
|
+
/**
|
|
852
|
+
* Verify an AdCP RFC 9421 webhook signature and parse the event.
|
|
853
|
+
*
|
|
854
|
+
* Checks, in order: header pair present → covered components complete → tag is
|
|
855
|
+
* the webhook tag → signature window sane and unexpired → key resolvable and
|
|
856
|
+
* purpose-valid → body digest matches → signature verifies.
|
|
857
|
+
*
|
|
858
|
+
* Replay defence beyond the window (per-`nonce` dedup) is the caller's — the
|
|
859
|
+
* nonce is exposed on the return value for exactly that.
|
|
860
|
+
*
|
|
861
|
+
* @throws TrillboardsError(authentication_error / webhook_signature_invalid)
|
|
862
|
+
*
|
|
863
|
+
* @example
|
|
864
|
+
* import { Webhooks } from '@trillboards/api-client';
|
|
865
|
+
*
|
|
866
|
+
* const { keys } = await (await fetch(
|
|
867
|
+
* 'https://api.trillboards.com/.well-known/jwks.json',
|
|
868
|
+
* )).json();
|
|
869
|
+
*
|
|
870
|
+
* app.post('/adcp/webhook', express.raw({ type: 'application/json' }), (req, res) => {
|
|
871
|
+
* const { event } = Webhooks.verifyAdcpWebhook({
|
|
872
|
+
* url: `https://buyer.example.com${req.originalUrl}`,
|
|
873
|
+
* headers: req.headers,
|
|
874
|
+
* payload: req.body,
|
|
875
|
+
* keys,
|
|
876
|
+
* });
|
|
877
|
+
* res.sendStatus(200);
|
|
878
|
+
* });
|
|
879
|
+
*/
|
|
880
|
+
declare function verifyAdcpWebhook(opts: VerifyAdcpWebhookOptions): {
|
|
881
|
+
event: WebhookEvent;
|
|
882
|
+
keyid: string;
|
|
883
|
+
nonce: string;
|
|
884
|
+
expires: number;
|
|
885
|
+
};
|
|
824
886
|
/** Namespaced export — matches the Stripe-style API. */
|
|
825
887
|
declare const Webhooks: {
|
|
826
888
|
constructEvent: typeof constructEvent;
|
|
827
889
|
constructEventLegacy: typeof constructEventLegacy;
|
|
828
890
|
parseSignatureHeader: typeof parseSignatureHeader;
|
|
891
|
+
verifyAdcpWebhook: typeof verifyAdcpWebhook;
|
|
829
892
|
};
|
|
830
893
|
|
|
831
894
|
/**
|
|
832
895
|
* SDK version constant. Sent in User-Agent and X-Trillboards-Client headers.
|
|
833
896
|
* Keep in sync with package.json version.
|
|
834
897
|
*/
|
|
835
|
-
declare const VERSION = "1.
|
|
898
|
+
declare const VERSION = "1.2.0";
|
|
836
899
|
|
|
837
|
-
export { AnalyticsClient, AnalyticsData, type ApiErrorType, AudienceClient, AudienceData, BillingClient, BillingInfo, type ClientOptions, type ConstructEventOptions, type ContentFitMode, CreatePartnerSensingRouteRequest, DataClient, Device, DevicesClient, DisplaySettingsInput, DisplaySettingsResponse, EarningsClient, EarningsData, type ErrorDetail, ErrorEntry, type ErrorResponseBody, ErrorsClient, FleetClient, FleetCommand, Impression, ImpressionsClient, type ListResponse, type ParsedSignatureHeader, PartnerDeviceBatchRegistrationInput, PartnerDeviceRegistrationInput, type RequestOptions, type RetryOptions, type RouteAction, type RouteChannel, type RouteCondition, type RouteConditionOp, type RouteDigest, type RouteDigestSummary, type RouteWebhookEvent, RoutesClient, Screen, ScreensClient, SensingRoute, type SensingThresholdCrossedEvent, TrillboardsClient, TrillboardsError, type TrillboardsErrorOptions, UpdatePartnerSensingRouteRequest, VERSION, Webhook, WebhookEvent, type WebhookHeaders, Webhooks, WebhooksClient, constructEvent, constructEventLegacy, parseSignatureHeader, retryWithBackoff };
|
|
900
|
+
export { ADCP_WEBHOOK_SIGNING_TAG, type AdcpJwk, AnalyticsClient, AnalyticsData, type ApiErrorType, AudienceClient, AudienceData, BillingClient, BillingInfo, type ClientOptions, type ConstructEventOptions, type ContentFitMode, CreatePartnerSensingRouteRequest, DataClient, Device, DevicesClient, DisplaySettingsInput, DisplaySettingsResponse, EarningsClient, EarningsData, type ErrorDetail, ErrorEntry, type ErrorResponseBody, ErrorsClient, FleetClient, FleetCommand, Impression, ImpressionsClient, type ListResponse, type ParsedSignatureHeader, PartnerDeviceBatchRegistrationInput, PartnerDeviceRegistrationInput, type RequestOptions, type RetryOptions, type RouteAction, type RouteChannel, type RouteCondition, type RouteConditionOp, type RouteDigest, type RouteDigestSummary, type RouteWebhookEvent, RoutesClient, Screen, ScreensClient, SensingRoute, type SensingThresholdCrossedEvent, TrillboardsClient, TrillboardsError, type TrillboardsErrorOptions, UpdatePartnerSensingRouteRequest, VERSION, type VerifyAdcpWebhookOptions, Webhook, WebhookEvent, type WebhookHeaders, Webhooks, WebhooksClient, constructEvent, constructEventLegacy, parseSignatureHeader, retryWithBackoff, verifyAdcpWebhook };
|
package/dist/index.d.ts
CHANGED
|
@@ -821,17 +821,80 @@ declare function constructEvent(payload: string | Buffer, headers: WebhookHeader
|
|
|
821
821
|
* legacy shim preserves only for migration compatibility.
|
|
822
822
|
*/
|
|
823
823
|
declare function constructEventLegacy(payload: string | Buffer, signature: string, secret: string): WebhookEvent;
|
|
824
|
+
/** RFC 9421 `tag` Trillboards stamps on outbound AdCP webhooks. */
|
|
825
|
+
declare const ADCP_WEBHOOK_SIGNING_TAG = "adcp/webhook-signing/v1";
|
|
826
|
+
/** An Ed25519 public key from the Trillboards JWKS. */
|
|
827
|
+
interface AdcpJwk {
|
|
828
|
+
kty: string;
|
|
829
|
+
crv: string;
|
|
830
|
+
x: string;
|
|
831
|
+
kid: string;
|
|
832
|
+
alg?: string;
|
|
833
|
+
use?: string;
|
|
834
|
+
key_ops?: string[];
|
|
835
|
+
adcp_use?: string;
|
|
836
|
+
}
|
|
837
|
+
interface VerifyAdcpWebhookOptions {
|
|
838
|
+
/** Absolute URL the webhook was delivered to. Must be the URL you published. */
|
|
839
|
+
url: string;
|
|
840
|
+
/** HTTP method of the delivery. Defaults to POST. */
|
|
841
|
+
method?: string;
|
|
842
|
+
/** Delivery headers (case-insensitive). */
|
|
843
|
+
headers: WebhookHeaders;
|
|
844
|
+
/** Raw request body — EXACTLY the bytes received, never a re-stringify. */
|
|
845
|
+
payload: string | Buffer;
|
|
846
|
+
/** Keys from https://api.trillboards.com/.well-known/jwks.json */
|
|
847
|
+
keys: AdcpJwk[];
|
|
848
|
+
/** Override the verifier's clock (seconds). Tests only. */
|
|
849
|
+
nowSec?: number;
|
|
850
|
+
}
|
|
851
|
+
/**
|
|
852
|
+
* Verify an AdCP RFC 9421 webhook signature and parse the event.
|
|
853
|
+
*
|
|
854
|
+
* Checks, in order: header pair present → covered components complete → tag is
|
|
855
|
+
* the webhook tag → signature window sane and unexpired → key resolvable and
|
|
856
|
+
* purpose-valid → body digest matches → signature verifies.
|
|
857
|
+
*
|
|
858
|
+
* Replay defence beyond the window (per-`nonce` dedup) is the caller's — the
|
|
859
|
+
* nonce is exposed on the return value for exactly that.
|
|
860
|
+
*
|
|
861
|
+
* @throws TrillboardsError(authentication_error / webhook_signature_invalid)
|
|
862
|
+
*
|
|
863
|
+
* @example
|
|
864
|
+
* import { Webhooks } from '@trillboards/api-client';
|
|
865
|
+
*
|
|
866
|
+
* const { keys } = await (await fetch(
|
|
867
|
+
* 'https://api.trillboards.com/.well-known/jwks.json',
|
|
868
|
+
* )).json();
|
|
869
|
+
*
|
|
870
|
+
* app.post('/adcp/webhook', express.raw({ type: 'application/json' }), (req, res) => {
|
|
871
|
+
* const { event } = Webhooks.verifyAdcpWebhook({
|
|
872
|
+
* url: `https://buyer.example.com${req.originalUrl}`,
|
|
873
|
+
* headers: req.headers,
|
|
874
|
+
* payload: req.body,
|
|
875
|
+
* keys,
|
|
876
|
+
* });
|
|
877
|
+
* res.sendStatus(200);
|
|
878
|
+
* });
|
|
879
|
+
*/
|
|
880
|
+
declare function verifyAdcpWebhook(opts: VerifyAdcpWebhookOptions): {
|
|
881
|
+
event: WebhookEvent;
|
|
882
|
+
keyid: string;
|
|
883
|
+
nonce: string;
|
|
884
|
+
expires: number;
|
|
885
|
+
};
|
|
824
886
|
/** Namespaced export — matches the Stripe-style API. */
|
|
825
887
|
declare const Webhooks: {
|
|
826
888
|
constructEvent: typeof constructEvent;
|
|
827
889
|
constructEventLegacy: typeof constructEventLegacy;
|
|
828
890
|
parseSignatureHeader: typeof parseSignatureHeader;
|
|
891
|
+
verifyAdcpWebhook: typeof verifyAdcpWebhook;
|
|
829
892
|
};
|
|
830
893
|
|
|
831
894
|
/**
|
|
832
895
|
* SDK version constant. Sent in User-Agent and X-Trillboards-Client headers.
|
|
833
896
|
* Keep in sync with package.json version.
|
|
834
897
|
*/
|
|
835
|
-
declare const VERSION = "1.
|
|
898
|
+
declare const VERSION = "1.2.0";
|
|
836
899
|
|
|
837
|
-
export { AnalyticsClient, AnalyticsData, type ApiErrorType, AudienceClient, AudienceData, BillingClient, BillingInfo, type ClientOptions, type ConstructEventOptions, type ContentFitMode, CreatePartnerSensingRouteRequest, DataClient, Device, DevicesClient, DisplaySettingsInput, DisplaySettingsResponse, EarningsClient, EarningsData, type ErrorDetail, ErrorEntry, type ErrorResponseBody, ErrorsClient, FleetClient, FleetCommand, Impression, ImpressionsClient, type ListResponse, type ParsedSignatureHeader, PartnerDeviceBatchRegistrationInput, PartnerDeviceRegistrationInput, type RequestOptions, type RetryOptions, type RouteAction, type RouteChannel, type RouteCondition, type RouteConditionOp, type RouteDigest, type RouteDigestSummary, type RouteWebhookEvent, RoutesClient, Screen, ScreensClient, SensingRoute, type SensingThresholdCrossedEvent, TrillboardsClient, TrillboardsError, type TrillboardsErrorOptions, UpdatePartnerSensingRouteRequest, VERSION, Webhook, WebhookEvent, type WebhookHeaders, Webhooks, WebhooksClient, constructEvent, constructEventLegacy, parseSignatureHeader, retryWithBackoff };
|
|
900
|
+
export { ADCP_WEBHOOK_SIGNING_TAG, type AdcpJwk, AnalyticsClient, AnalyticsData, type ApiErrorType, AudienceClient, AudienceData, BillingClient, BillingInfo, type ClientOptions, type ConstructEventOptions, type ContentFitMode, CreatePartnerSensingRouteRequest, DataClient, Device, DevicesClient, DisplaySettingsInput, DisplaySettingsResponse, EarningsClient, EarningsData, type ErrorDetail, ErrorEntry, type ErrorResponseBody, ErrorsClient, FleetClient, FleetCommand, Impression, ImpressionsClient, type ListResponse, type ParsedSignatureHeader, PartnerDeviceBatchRegistrationInput, PartnerDeviceRegistrationInput, type RequestOptions, type RetryOptions, type RouteAction, type RouteChannel, type RouteCondition, type RouteConditionOp, type RouteDigest, type RouteDigestSummary, type RouteWebhookEvent, RoutesClient, Screen, ScreensClient, SensingRoute, type SensingThresholdCrossedEvent, TrillboardsClient, TrillboardsError, type TrillboardsErrorOptions, UpdatePartnerSensingRouteRequest, VERSION, type VerifyAdcpWebhookOptions, Webhook, WebhookEvent, type WebhookHeaders, Webhooks, WebhooksClient, constructEvent, constructEventLegacy, parseSignatureHeader, retryWithBackoff, verifyAdcpWebhook };
|
package/dist/index.js
CHANGED
|
@@ -2,6 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
var crypto = require('crypto');
|
|
4
4
|
|
|
5
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
6
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
7
|
+
}) : x)(function(x) {
|
|
8
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
9
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
10
|
+
});
|
|
11
|
+
|
|
5
12
|
// src/error.ts
|
|
6
13
|
var TrillboardsError = class _TrillboardsError extends Error {
|
|
7
14
|
constructor(opts) {
|
|
@@ -72,7 +79,7 @@ var TrillboardsError = class _TrillboardsError extends Error {
|
|
|
72
79
|
};
|
|
73
80
|
|
|
74
81
|
// src/version.ts
|
|
75
|
-
var VERSION = "1.
|
|
82
|
+
var VERSION = "1.2.0";
|
|
76
83
|
|
|
77
84
|
// src/http.ts
|
|
78
85
|
var PUBLIC_PATHS = ["/v1/errors", "/v1/changelog", "/api/discover", "/v1/health"];
|
|
@@ -630,20 +637,183 @@ function constructEventLegacy(payload, signature, secret) {
|
|
|
630
637
|
}
|
|
631
638
|
return JSON.parse(payloadStr);
|
|
632
639
|
}
|
|
640
|
+
var ADCP_WEBHOOK_SIGNING_TAG = "adcp/webhook-signing/v1";
|
|
641
|
+
var ADCP_WEBHOOK_COMPONENTS = [
|
|
642
|
+
"@method",
|
|
643
|
+
"@target-uri",
|
|
644
|
+
"@authority",
|
|
645
|
+
"content-type",
|
|
646
|
+
"content-digest"
|
|
647
|
+
];
|
|
648
|
+
var ADCP_MAX_WINDOW_SECONDS = 300;
|
|
649
|
+
function splitDictionary(headerValue) {
|
|
650
|
+
const members = [];
|
|
651
|
+
let depth = 0;
|
|
652
|
+
let quoted = false;
|
|
653
|
+
let start = 0;
|
|
654
|
+
for (let i = 0; i < headerValue.length; i++) {
|
|
655
|
+
const ch = headerValue[i];
|
|
656
|
+
if (quoted) {
|
|
657
|
+
if (ch === "\\") i++;
|
|
658
|
+
else if (ch === '"') quoted = false;
|
|
659
|
+
continue;
|
|
660
|
+
}
|
|
661
|
+
if (ch === '"') quoted = true;
|
|
662
|
+
else if (ch === "(") depth++;
|
|
663
|
+
else if (ch === ")") depth--;
|
|
664
|
+
else if (ch === "," && depth === 0) {
|
|
665
|
+
members.push(headerValue.slice(start, i).trim());
|
|
666
|
+
start = i + 1;
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
members.push(headerValue.slice(start).trim());
|
|
670
|
+
return members.filter((member) => member.length > 0);
|
|
671
|
+
}
|
|
672
|
+
function dictionaryMember(headerValue, label) {
|
|
673
|
+
return splitDictionary(headerValue).find((member) => member.startsWith(`${label}=`));
|
|
674
|
+
}
|
|
675
|
+
var ADCP_SIGNATURE_LABEL = "sig1";
|
|
676
|
+
function selectAdcpSignatureInput(signatureInput) {
|
|
677
|
+
const members = splitDictionary(signatureInput);
|
|
678
|
+
const named = members.find((member) => member.startsWith(`${ADCP_SIGNATURE_LABEL}=`));
|
|
679
|
+
if (named) return { label: ADCP_SIGNATURE_LABEL, member: named };
|
|
680
|
+
const tagged = members.find((member) => member.includes(`tag="${ADCP_WEBHOOK_SIGNING_TAG}"`));
|
|
681
|
+
if (tagged) return { label: tagged.slice(0, tagged.indexOf("=")), member: tagged };
|
|
682
|
+
throw authError(
|
|
683
|
+
`AdCP webhook has no "${ADCP_SIGNATURE_LABEL}" signature member and no member tagged "${ADCP_WEBHOOK_SIGNING_TAG}".`
|
|
684
|
+
);
|
|
685
|
+
}
|
|
686
|
+
function canonicalTargetUri(rawUrl) {
|
|
687
|
+
const parsed = new URL(rawUrl);
|
|
688
|
+
const beforeFragment = rawUrl.split("#", 1)[0];
|
|
689
|
+
const search = parsed.search || (beforeFragment.endsWith("?") ? "?" : "");
|
|
690
|
+
return `${parsed.protocol}//${parsed.host}${parsed.pathname}${search}`.replace(
|
|
691
|
+
/%([0-9a-fA-F]{2})/g,
|
|
692
|
+
(match, hex) => {
|
|
693
|
+
const code = parseInt(hex, 16);
|
|
694
|
+
const unreserved = code >= 65 && code <= 90 || code >= 97 && code <= 122 || code >= 48 && code <= 57 || code === 45 || code === 46 || code === 95 || code === 126;
|
|
695
|
+
return unreserved ? String.fromCharCode(code) : `%${hex.toUpperCase()}`;
|
|
696
|
+
}
|
|
697
|
+
);
|
|
698
|
+
}
|
|
699
|
+
function verifyAdcpWebhook(opts) {
|
|
700
|
+
const { createHash, createPublicKey, verify: cryptoVerify } = __require("crypto");
|
|
701
|
+
const signatureInput = getHeader(opts.headers, "Signature-Input");
|
|
702
|
+
const signature = getHeader(opts.headers, "Signature");
|
|
703
|
+
if (!signatureInput || !signature) {
|
|
704
|
+
throw authError("AdCP webhook is missing the Signature / Signature-Input header pair.");
|
|
705
|
+
}
|
|
706
|
+
const { label, member: inputMember } = selectAdcpSignatureInput(signatureInput);
|
|
707
|
+
const signatureMember = dictionaryMember(signature, label);
|
|
708
|
+
if (!signatureMember) {
|
|
709
|
+
throw authError(`AdCP webhook Signature has no "${label}" member to match Signature-Input.`);
|
|
710
|
+
}
|
|
711
|
+
const paramsStart = inputMember.indexOf("(");
|
|
712
|
+
const paramsEnd = inputMember.indexOf(")");
|
|
713
|
+
if (paramsStart < 0 || paramsEnd < 0) {
|
|
714
|
+
throw authError("AdCP webhook Signature-Input is malformed.");
|
|
715
|
+
}
|
|
716
|
+
const components = inputMember.slice(paramsStart + 1, paramsEnd).split(" ").map((component) => component.replace(/^"|"$/g, "")).filter((component) => component.length > 0);
|
|
717
|
+
const readParam = (name) => {
|
|
718
|
+
const match = new RegExp(`;${name}=("?)([^";]+)\\1`).exec(inputMember);
|
|
719
|
+
return match ? match[2] : void 0;
|
|
720
|
+
};
|
|
721
|
+
const created = Number(readParam("created"));
|
|
722
|
+
const expires = Number(readParam("expires"));
|
|
723
|
+
const nonce = readParam("nonce");
|
|
724
|
+
const keyid = readParam("keyid");
|
|
725
|
+
const alg = readParam("alg");
|
|
726
|
+
const tag = readParam("tag");
|
|
727
|
+
if (!nonce || !keyid || !alg || !Number.isFinite(created) || !Number.isFinite(expires)) {
|
|
728
|
+
throw authError("AdCP webhook signature is missing a required parameter.");
|
|
729
|
+
}
|
|
730
|
+
if (tag !== ADCP_WEBHOOK_SIGNING_TAG) {
|
|
731
|
+
throw authError(`AdCP webhook signature has tag "${tag}", expected "${ADCP_WEBHOOK_SIGNING_TAG}".`);
|
|
732
|
+
}
|
|
733
|
+
if (alg !== "ed25519") {
|
|
734
|
+
throw authError(`AdCP webhook signature algorithm "${alg}" is not allowed.`);
|
|
735
|
+
}
|
|
736
|
+
for (const required of ADCP_WEBHOOK_COMPONENTS) {
|
|
737
|
+
if (!components.includes(required)) {
|
|
738
|
+
throw authError(`AdCP webhook signature does not cover "${required}".`);
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
if (expires <= created || expires - created > ADCP_MAX_WINDOW_SECONDS) {
|
|
742
|
+
throw authError("AdCP webhook signature window is invalid.");
|
|
743
|
+
}
|
|
744
|
+
const now = opts.nowSec ?? Math.floor(Date.now() / 1e3);
|
|
745
|
+
if (now > expires) {
|
|
746
|
+
throw authError("AdCP webhook signature has expired. Possible replay attack.");
|
|
747
|
+
}
|
|
748
|
+
const jwk = opts.keys.find((key) => key.kid === keyid);
|
|
749
|
+
if (!jwk) {
|
|
750
|
+
throw authError(`AdCP webhook signed with unknown key "${keyid}". Refresh the JWKS.`);
|
|
751
|
+
}
|
|
752
|
+
if (jwk.key_ops && !jwk.key_ops.includes("verify")) {
|
|
753
|
+
throw authError(`AdCP webhook key "${keyid}" is not a verification key.`);
|
|
754
|
+
}
|
|
755
|
+
if (jwk.adcp_use && jwk.adcp_use !== "request-signing" && jwk.adcp_use !== "webhook-signing") {
|
|
756
|
+
throw authError(`AdCP webhook key "${keyid}" has adcp_use "${jwk.adcp_use}".`);
|
|
757
|
+
}
|
|
758
|
+
const payloadBuf = typeof opts.payload === "string" ? Buffer.from(opts.payload, "utf8") : opts.payload;
|
|
759
|
+
const contentDigest = getHeader(opts.headers, "Content-Digest");
|
|
760
|
+
const expectedDigest = `sha-256=:${createHash("sha256").update(payloadBuf).digest("base64")}:`;
|
|
761
|
+
if (!contentDigest || contentDigest !== expectedDigest) {
|
|
762
|
+
throw authError("AdCP webhook Content-Digest does not match the body.");
|
|
763
|
+
}
|
|
764
|
+
const method = (opts.method ?? "POST").toUpperCase();
|
|
765
|
+
const target = new URL(opts.url);
|
|
766
|
+
const baseLines = components.map((component) => {
|
|
767
|
+
switch (component) {
|
|
768
|
+
case "@method":
|
|
769
|
+
return `"@method": ${method}`;
|
|
770
|
+
case "@target-uri":
|
|
771
|
+
return `"@target-uri": ${canonicalTargetUri(opts.url)}`;
|
|
772
|
+
case "@authority":
|
|
773
|
+
return `"@authority": ${target.host.toLowerCase()}`;
|
|
774
|
+
default: {
|
|
775
|
+
const value = getHeader(opts.headers, component);
|
|
776
|
+
if (value === void 0) {
|
|
777
|
+
throw authError(`AdCP webhook covers "${component}" but the header is absent.`);
|
|
778
|
+
}
|
|
779
|
+
return `"${component}": ${value.trim()}`;
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
});
|
|
783
|
+
baseLines.push(`"@signature-params": ${inputMember.slice(label.length + 1)}`);
|
|
784
|
+
const signatureBytes = Buffer.from(
|
|
785
|
+
signatureMember.slice(label.length + 1).replace(/^:|:$/g, ""),
|
|
786
|
+
"base64url"
|
|
787
|
+
);
|
|
788
|
+
const publicKey = createPublicKey({ key: jwk, format: "jwk" });
|
|
789
|
+
if (!cryptoVerify(null, Buffer.from(baseLines.join("\n"), "utf8"), publicKey, signatureBytes)) {
|
|
790
|
+
throw authError(
|
|
791
|
+
"AdCP webhook signature verification failed. The delivery may have been tampered with."
|
|
792
|
+
);
|
|
793
|
+
}
|
|
794
|
+
return {
|
|
795
|
+
event: JSON.parse(payloadBuf.toString("utf8")),
|
|
796
|
+
keyid,
|
|
797
|
+
nonce,
|
|
798
|
+
expires
|
|
799
|
+
};
|
|
800
|
+
}
|
|
633
801
|
var Webhooks = {
|
|
634
802
|
constructEvent,
|
|
635
803
|
constructEventLegacy,
|
|
636
|
-
parseSignatureHeader
|
|
804
|
+
parseSignatureHeader,
|
|
805
|
+
verifyAdcpWebhook
|
|
637
806
|
};
|
|
638
807
|
|
|
639
808
|
// src/generated/index.ts
|
|
640
809
|
var generatedTypes = {
|
|
641
|
-
schemaCount:
|
|
642
|
-
operationCount:
|
|
643
|
-
schemas: ["Ad", "AdSettings", "AnalyticsData", "AudienceAnalyticsResponse", "AudienceAttentionPrediction", "AudienceData", "AudienceExportResponse", "AudienceLoyaltyFeaturesRow", "AudienceMetricsResponse", "AudienceSegment", "AudienceSocialAttention", "BatchResult", "BillingInfo", "BleScanResultData", "ChangelogEntry", "ChangelogResponse", "ContentFitSettings", "ContentPreferencesInput", "ContentPreferencesResponse", "ContentPreset", "CreatePartnerSensingRouteRequest", "CreativeClassification", "DashboardResponse", "DataExportSummaryResponse", "DemographicsResponse", "Device", "DeviceCapabilityPayload", "DeviceSummary", "DisplaySettingsInput", "DisplaySettingsResponse", "EarningsBreakdownResponse", "EarningsData", "EarningsResponse", "Error", "ErrorEntry", "FleetCommand", "FleetCommandEvent", "HeaderBiddingSettings", "HttpProbeResult", "Impression", "MdnsNetworkDevice", "Pagination", "Partner", "PartnerAdDeliveryProfile", "PartnerDefaultStream", "PartnerDefaultStreamCreate", "PartnerDefaultStreamUpdate", "PartnerDeviceBatchRegistrationInput", "PartnerDeviceHeartbeatBody", "PartnerDeviceRegistrationInput", "PartnerHeartbeatCommand", "PartnerHeartbeatData", "PartnerHeartbeatMeasurementMetadata", "PartnerHeartbeatQueryData", "PartnerHeartbeatQueryResponse", "PartnerHeartbeatRequest", "PartnerHeartbeatResponse", "PartnerHeartbeatTelemetry", "PartnerLBarContent", "PartnerLBarDisplayPreferences", "PartnerLBarSettings", "PartnerMonetizationRequirements", "PartnerNetworkConfig", "PartnerNetworkConfigUpdate", "PartnerPlay", "PartnerRegistrationDisplaySettings", "PartnerRequirement", "PartnerSafeHeartbeatTelemetry", "PartnerSelfPromoAd", "PartnerSelfPromoCreate", "PartnerSelfPromoPlacement", "PartnerStats", "PayoutResponse", "PlaygroundResult", "PlaygroundValidationError", "ProgrammaticSettingsInput", "ProgrammaticSettingsResponse", "RequestMetadataResponse", "ResolvedContentPolicy", "ResolvedSensingConfig", "RolePermissions", "SDKConfig", "Screen", "ScreenAnalyticsResponse", "ScreenGroup", "ScreenGroupContentPolicy", "ScreenGroupContentPolicyInput", "ScreenGroupEvent", "ScreenGroupSensingConfig", "ScreenGroupSensingConfigInput", "SensingPerformanceMode", "SensingRoute", "SensingRouteAction", "SensingRouteCondition", "SensingSettingsResponse", "SensingThresholdCrossedEvent", "SsdpDeviceInfo", "TeamInviteResponse", "TeamMember", "Transaction", "UpdatePartnerSensingRouteRequest", "VastWaterfall", "VastWaterfallSource", "Webhook", "WebhookDelivery", "WebhookEvent", "WifiEnvironmentSnapshot", "WifiScanResult"],
|
|
644
|
-
operations: ["listPartnerSelfPromoAds", "createPartnerSelfPromoAd", "listPartnerSelfPromoPlacements", "schedulePartnerSelfPromo", "createPartnerSelfPromoUpload", "disableSelfPromoAd", "registerAgent", "getAnalytics", "getAudienceContinuity", "getAudienceProfile", "getBleSummary", "getContentPerformance", "searchCreativeCatalog", "getCreativeScenes", "getCrossScreenFlow", "getPartnerAnalyticsDashboard", "getDeviceSuiteSignature", "exportPartnerAnalytics", "exportPartnerAdsByDay", "getFleetHourly", "getHaloFunnel", "getLoyaltyClock", "getNetworkSummary", "getNetworkTrend", "getProximitySummary", "getRevenueAttentionCorrelation", "getSceneFeed", "getSceneIntelligence", "rotateApiKey", "getAudienceAnalytics", "exportAudienceData", "getAudienceHeatmap", "getLiveAudience", "getLookalikeScreens", "predictScreenAudience", "getResolvedHouseholdReach", "listAudienceSegments", "createAudienceSegment", "deleteAudienceSegment", "getAudienceSegment", "updateAudienceSegment", "matchAudienceSegment", "estimateSegmentReach", "generateSegmentVastTag", "getAudienceStats", "cancelBillingSubscription", "purchaseCredits", "createCreditCheckout", "listBillingPlans", "setupBilling", "createBillingSubscription", "getBillingSubscription", "getBillingUsage", "
|
|
810
|
+
schemaCount: 112,
|
|
811
|
+
operationCount: 211,
|
|
812
|
+
schemas: ["Ad", "AdSettings", "AdvertisingIdentity", "AnalyticsData", "AudienceAnalyticsResponse", "AudienceAttentionPrediction", "AudienceData", "AudienceExportResponse", "AudienceLoyaltyFeaturesRow", "AudienceMetricsResponse", "AudienceSegment", "AudienceSocialAttention", "BatchResult", "BillingInfo", "BleScanResultData", "ChangelogEntry", "ChangelogResponse", "ContentFitSettings", "ContentPreferencesInput", "ContentPreferencesResponse", "ContentPreset", "CreatePartnerSensingRouteRequest", "CreativeClassification", "DashboardResponse", "DataExportSummaryResponse", "DemographicsResponse", "Device", "DeviceCapabilityPayload", "DeviceSummary", "DisplaySettingsInput", "DisplaySettingsResponse", "EarningsBreakdownResponse", "EarningsData", "EarningsResponse", "Error", "ErrorEntry", "ExtendedIdentity", "FleetCommand", "FleetCommandEvent", "HeaderBiddingSettings", "HttpProbeResult", "Impression", "MdnsNetworkDevice", "MeasurementMethodology", "Pagination", "Partner", "PartnerAdDeliveryProfile", "PartnerCreativeWarnings", "PartnerDefaultStream", "PartnerDefaultStreamCreate", "PartnerDefaultStreamUpdate", "PartnerDeviceBatchRegistrationInput", "PartnerDeviceHeartbeatBody", "PartnerDeviceRegistrationInput", "PartnerHeartbeatCommand", "PartnerHeartbeatData", "PartnerHeartbeatMeasurementMetadata", "PartnerHeartbeatQueryData", "PartnerHeartbeatQueryResponse", "PartnerHeartbeatRequest", "PartnerHeartbeatResponse", "PartnerHeartbeatTelemetry", "PartnerLBarContent", "PartnerLBarDisplayPreferences", "PartnerLBarSettings", "PartnerMonetizationRequirements", "PartnerNetworkConfig", "PartnerNetworkConfigUpdate", "PartnerPlay", "PartnerRegistrationDisplaySettings", "PartnerRequirement", "PartnerSafeHeartbeatTelemetry", "PartnerSelfPromoAd", "PartnerSelfPromoCreate", "PartnerSelfPromoPlacement", "PartnerStats", "PayoutResponse", "PlaygroundResult", "PlaygroundValidationError", "ProgrammaticSettingsInput", "ProgrammaticSettingsResponse", "RequestMetadataResponse", "ResolvedContentPolicy", "ResolvedSensingConfig", "RolePermissions", "SDKConfig", "Screen", "ScreenAnalyticsResponse", "ScreenGroup", "ScreenGroupContentPolicy", "ScreenGroupContentPolicyInput", "ScreenGroupEvent", "ScreenGroupSensingConfig", "ScreenGroupSensingConfigInput", "SensingPerformanceMode", "SensingRoute", "SensingRouteAction", "SensingRouteCondition", "SensingSettingsResponse", "SensingThresholdCrossedEvent", "SsdpDeviceInfo", "TeamInviteResponse", "TeamMember", "Transaction", "UpdatePartnerSensingRouteRequest", "VastWaterfall", "VastWaterfallSource", "Webhook", "WebhookDelivery", "WebhookEvent", "WifiEnvironmentSnapshot", "WifiScanResult"],
|
|
813
|
+
operations: ["listPartnerSelfPromoAds", "createPartnerSelfPromoAd", "listPartnerSelfPromoPlacements", "schedulePartnerSelfPromo", "createPartnerSelfPromoUpload", "disableSelfPromoAd", "registerAgent", "getAnalytics", "getAudienceContinuity", "getAudienceProfile", "getBleSummary", "getContentPerformance", "searchCreativeCatalog", "getCreativeScenes", "getCrossScreenFlow", "getPartnerAnalyticsDashboard", "getDeviceSuiteSignature", "exportPartnerAnalytics", "exportPartnerAdsByDay", "getFleetHourly", "getHaloFunnel", "getLoyaltyClock", "getNetworkSummary", "getNetworkTrend", "getProximitySummary", "getRevenueAttentionCorrelation", "getSceneFeed", "getSceneIntelligence", "rotateApiKey", "getAudienceAnalytics", "exportAudienceData", "getAudienceHeatmap", "getLiveAudience", "getLookalikeScreens", "predictScreenAudience", "getResolvedHouseholdReach", "listAudienceSegments", "createAudienceSegment", "deleteAudienceSegment", "getAudienceSegment", "updateAudienceSegment", "matchAudienceSegment", "estimateSegmentReach", "generateSegmentVastTag", "getAudienceStats", "cancelBillingSubscription", "purchaseCredits", "createCreditCheckout", "listBillingPlans", "setupBilling", "createBillingSubscription", "getBillingSubscription", "getBillingUsage", "getBillingUsageSummary", "stripeWebhookCallback", "getConnectDashboard", "getConnectEarningsChart", "getConnectScreenHealth", "listContentPresets", "getContentPreset", "submitPartnerCreative", "getPartnerCreative", "getDashboard", "exportProximityGraphEdges", "getDataExportSummary", "exportDataset", "getGeohashAudiencePanel", "getOutcomeAttribution", "registerDevice", "deleteDevice", "getDevice", "updateDevice", "getAdsForDevice", "ackDeviceCommand", "getDeviceHeartbeat", "heartbeat", "recordProgrammaticEvent", "listDevices", "registerDeviceV2", "registerDevicesBatch", "batchUpdateDeviceOrientation", "reconcileDevices", "partnerDocsBrowser", "getEarnings", "getEarningsBreakdown", "requestPayout", "getTransactions", "getActiveSSEConnections", "subscribeToEvents", "getFleetCommand", "getFleetAnalytics", "pushFleetCommand", "getFleetOverview", "updateFleetSettings", "recordImpressionGet", "recordImpressionPost", "recordImpressionsBatch", "getPartnerInfo", "listIntentCategories", "getIntentCategory", "getIntentPricing", "getMeasurementMethodology", "getMeasurementMethodologyJson", "getNetworkConfig", "patchNetworkConfig", "updateNetworkConfig", "getNetworkTrends", "partnerPing", "partnerPingAuth", "recordPartnerPlay", "recordPartnerPlaysBatch", "getPricing", "recordProgrammaticEventLegacyAlias", "quickStart", "registerPartner", "generateSandboxEvent", "resetSandbox", "createSandboxScreen", "getSandboxStatus", "listScreenGroups", "createScreenGroup", "deleteScreenGroup", "getScreenGroup", "updateScreenGroup", "getScreenGroupAnalytics", "createScreenGroupCommand", "getScreenGroupContentPolicy", "updateScreenGroupContentPolicy", "removeScreenGroupMembers", "listScreenGroupMembers", "addScreenGroupMembers", "getScreenGroupSensingConfig", "updateScreenGroupSensingConfig", "getScreenAudienceSegments", "batchUpdateContentPreferences", "batchUpdateDisplaySettings", "batchUpdateProgrammaticSettings", "resolvePartnerScreen", "getScreenAnalytics", "getPartnerScreenAtmosphere", "getPartnerScreenIntelligence", "getPartnerScreenTasteProfile", "getPartnerScreenTraffic", "getPartnerScreenVAS", "applyContentPreset", "getAudienceMetrics", "getContentPreferences", "patchContentPreferences", "updateContentPreferences", "listPartnerDefaultStreams", "createPartnerDefaultStream", "createPartnerDefaultStreamImages", "reorderPartnerDefaultStreams", "createPartnerDefaultStreamUpload", "deletePartnerDefaultStream", "updatePartnerDefaultStream", "getScreenDemographics", "getDisplaySettings", "patchDisplaySettings", "updateDisplaySettings", "getPartnerLBarSettings", "updatePartnerLBarSettings", "updatePartnerLBarLiveUpdates", "getProgrammaticSettings", "patchProgrammaticSettings", "updateProgrammaticSettings", "getResolvedContentPolicy", "getResolvedSensingConfig", "getScreenSensingChips", "interpretScreenSensingIntent", "deployScreenSensingProfile", "getScreenSensingSettings", "updateScreenSensingSettings", "listPartnerSensingRoutes", "createPartnerSensingRoute", "deletePartnerSensingRoute", "getPartnerSensingRoute", "updatePartnerSensingRoute", "createStripeConnectAccount", "getStripeLoginLink", "getStripeOnboardingLink", "requestStripePayout", "getStripePayoutHistory", "getStripeAccountStatus", "handleStripeWebhook", "listTeamMembers", "acceptInvitation", "inviteTeamMember", "getInvitationByToken", "resendInvitation", "removeTeamMember", "changeTeamMemberRole", "getTrackingPublicKey", "recordTrackingBatch", "getUsageSummary", "getUsageDashboard", "getRateLimitStatus", "queryChangelog", "playgroundDryRun", "getRequestMetadata", "getVastConfig", "getVenuesNearby", "getVenueAtmosphere", "getVenueTraffic", "getVenueIntelligence", "listWebhooks", "createWebhook", "deleteWebhook", "updateWebhook", "getWebhookDeliveries", "testWebhook"]
|
|
645
814
|
};
|
|
646
815
|
|
|
816
|
+
exports.ADCP_WEBHOOK_SIGNING_TAG = ADCP_WEBHOOK_SIGNING_TAG;
|
|
647
817
|
exports.AnalyticsClient = AnalyticsClient;
|
|
648
818
|
exports.AudienceClient = AudienceClient;
|
|
649
819
|
exports.BillingClient = BillingClient;
|
|
@@ -665,5 +835,6 @@ exports.constructEventLegacy = constructEventLegacy;
|
|
|
665
835
|
exports.generatedTypes = generatedTypes;
|
|
666
836
|
exports.parseSignatureHeader = parseSignatureHeader;
|
|
667
837
|
exports.retryWithBackoff = retryWithBackoff;
|
|
838
|
+
exports.verifyAdcpWebhook = verifyAdcpWebhook;
|
|
668
839
|
//# sourceMappingURL=index.js.map
|
|
669
840
|
//# sourceMappingURL=index.js.map
|