@dealcrawl/sdk 2.11.1 → 2.12.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/README.md CHANGED
@@ -6,6 +6,46 @@ Official TypeScript SDK for the DealCrawl web scraping and crawling API.
6
6
  [![TypeScript](https://img.shields.io/badge/TypeScript-5.0+-blue.svg)](https://www.typescriptlang.org/)
7
7
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
8
8
 
9
+ ## What's New in v2.12.0 (January 2026) ✨
10
+
11
+ ### New Methods - 100% API Coverage
12
+
13
+ All missing API endpoints now have SDK methods (completes the 87% → 100% alignment):
14
+
15
+ **Status Resource (`client.status.*`)**
16
+
17
+ - `getJobErrors(jobId, options?)` - Get job errors without loading full results (useful for debugging large crawls)
18
+
19
+ **Data Resource (`client.data.*`)**
20
+
21
+ - `getJob(jobId)` - Get full job details with result
22
+ - `getJobResult(jobId)` - Get only the result of a completed job
23
+ - `exportJob(jobId, format)` - Export job in multiple formats (json, markdown, llm, csv)
24
+
25
+ **Webhooks Resource (`client.webhooks.*`)**
26
+
27
+ - `rotateSecret(webhookId, options)` - Rotate webhook secret with grace period support
28
+ - `getSecretStatus(webhookId)` - Check secret version and grace period status
29
+ - `verifySignature(options)` - Verify webhook signature and replay protection
30
+
31
+ ### Examples
32
+
33
+ ```typescript
34
+ // Get job errors for debugging
35
+ const errors = await client.status.getJobErrors("job_abc123", { limit: 50 });
36
+
37
+ // Export crawl results as Markdown
38
+ const markdown = await client.data.exportJob("crawl_123", "markdown");
39
+
40
+ // Rotate webhook secret with 24h grace period
41
+ await client.webhooks.rotateSecret("webhook_abc", {
42
+ newSecret: "new-secure-key",
43
+ gracePeriodHours: 24
44
+ });
45
+ ```
46
+
47
+ ---
48
+
9
49
  ## What's New in v2.11.1 (January 2026) 🐛
10
50
 
11
51
  ### Bug Fixes
package/dist/index.d.mts CHANGED
@@ -690,6 +690,22 @@ interface CancelJobResponse {
690
690
  jobId: string;
691
691
  message: string;
692
692
  }
693
+ /** Job error entry */
694
+ interface JobErrorEntry {
695
+ url?: string;
696
+ error: string;
697
+ timestamp?: string;
698
+ }
699
+ /** Job errors response */
700
+ interface JobErrorsResponse {
701
+ data: JobErrorEntry[];
702
+ pagination: PaginationInfo;
703
+ meta?: {
704
+ jobId: string;
705
+ status?: JobStatus$1;
706
+ offset?: number;
707
+ };
708
+ }
693
709
  /** Job summary in list */
694
710
  interface JobSummary {
695
711
  id: string;
@@ -769,6 +785,32 @@ interface ClientStatsResponse {
769
785
  }>;
770
786
  };
771
787
  }
788
+ /** Full job details response */
789
+ interface JobDetailsResponse {
790
+ id: string;
791
+ type: "scrape" | "crawl" | "dork" | "extract";
792
+ status: JobStatus$1;
793
+ input: Record<string, unknown>;
794
+ result?: unknown;
795
+ progress?: number;
796
+ error?: string;
797
+ dealsFound?: number;
798
+ avgDealScore?: number;
799
+ highestDealScore?: number;
800
+ checkpoint?: CheckpointInfo;
801
+ createdAt: string;
802
+ updatedAt: string;
803
+ completedAt?: string;
804
+ }
805
+ /** Job result response */
806
+ interface JobResultResponse {
807
+ jobId: string;
808
+ status: "completed";
809
+ result: unknown;
810
+ completedAt?: string;
811
+ }
812
+ /** Job export response (string for CSV/Markdown/LLM, object for JSON) */
813
+ type JobExportResponse = string | Record<string, unknown>;
772
814
  /** Webhook in list */
773
815
  interface WebhookItem {
774
816
  id: string;
@@ -824,6 +866,35 @@ interface TestWebhookResponse {
824
866
  responseTime?: number;
825
867
  error?: string;
826
868
  }
869
+ /** Rotate webhook secret response */
870
+ interface RotateSecretResponse {
871
+ success: boolean;
872
+ webhookId: string;
873
+ secretVersion: number;
874
+ previousSecretValidUntil: string;
875
+ gracePeriodHours: number;
876
+ message: string;
877
+ }
878
+ /** Webhook secret status response */
879
+ interface SecretStatusResponse {
880
+ webhookId: string;
881
+ hasSecret: boolean;
882
+ secretVersion: number;
883
+ lastRotatedAt?: string;
884
+ gracePeriod?: {
885
+ active: boolean;
886
+ previousVersion: number;
887
+ expiresAt: string;
888
+ } | null;
889
+ }
890
+ /** Verify webhook signature response */
891
+ interface VerifyWebhookResponse {
892
+ valid: boolean;
893
+ webhookId?: string;
894
+ message: string;
895
+ error?: string;
896
+ reason?: string;
897
+ }
827
898
  /** API key info (safe, without secret) */
828
899
  interface ApiKeyInfo {
829
900
  id: string;
@@ -2645,6 +2716,47 @@ declare class DataResource {
2645
2716
  * ```
2646
2717
  */
2647
2718
  getRecentJobs(limit?: number): Promise<ListJobsResponse>;
2719
+ /**
2720
+ * Get full job details with result
2721
+ *
2722
+ * @example
2723
+ * ```ts
2724
+ * const job = await client.data.getJob("job_abc123");
2725
+ * console.log(job.result);
2726
+ * console.log(job.dealsFound);
2727
+ * ```
2728
+ */
2729
+ getJob(jobId: string): Promise<JobDetailsResponse>;
2730
+ /**
2731
+ * Get only the result of a completed job
2732
+ *
2733
+ * @example
2734
+ * ```ts
2735
+ * const result = await client.data.getJobResult("job_abc123");
2736
+ * console.log(result.result);
2737
+ * ```
2738
+ */
2739
+ getJobResult(jobId: string): Promise<JobResultResponse>;
2740
+ /**
2741
+ * Export job results in various formats
2742
+ * Supports: json, markdown, llm, csv
2743
+ *
2744
+ * @example
2745
+ * ```ts
2746
+ * // Export as JSON
2747
+ * const jsonData = await client.data.exportJob("job_abc123", "json");
2748
+ *
2749
+ * // Export as Markdown
2750
+ * const markdown = await client.data.exportJob("job_abc123", "markdown");
2751
+ *
2752
+ * // Export as LLM-optimized format
2753
+ * const llmText = await client.data.exportJob("job_abc123", "llm");
2754
+ *
2755
+ * // Export as CSV
2756
+ * const csv = await client.data.exportJob("job_abc123", "csv");
2757
+ * ```
2758
+ */
2759
+ exportJob(jobId: string, format?: "json" | "markdown" | "llm" | "csv"): Promise<JobExportResponse>;
2648
2760
  /**
2649
2761
  * List all deals
2650
2762
  *
@@ -3537,6 +3649,24 @@ declare class StatusResource {
3537
3649
  * ```
3538
3650
  */
3539
3651
  getMetrics(jobId: string): Promise<JobMetricsResponse>;
3652
+ /**
3653
+ * Get errors from a job
3654
+ * Useful for debugging large crawls without loading full results
3655
+ *
3656
+ * @example
3657
+ * ```ts
3658
+ * const errors = await client.status.getJobErrors("job_abc123", {
3659
+ * limit: 50,
3660
+ * offset: 0
3661
+ * });
3662
+ * console.log(errors.data);
3663
+ * console.log(errors.pagination.total);
3664
+ * ```
3665
+ */
3666
+ getJobErrors(jobId: string, options?: {
3667
+ limit?: number;
3668
+ offset?: number;
3669
+ }): Promise<JobErrorsResponse>;
3540
3670
  /**
3541
3671
  * Cancel a pending or active job
3542
3672
  *
@@ -3701,6 +3831,65 @@ declare class WebhooksResource {
3701
3831
  * ```
3702
3832
  */
3703
3833
  getByEvent(event: CreateWebhookOptions["event"]): Promise<WebhookItem[]>;
3834
+ /**
3835
+ * Rotate webhook secret
3836
+ * Rotates the secret while keeping the old one valid during grace period
3837
+ *
3838
+ * @example
3839
+ * ```ts
3840
+ * const result = await client.webhooks.rotateSecret("webhook_abc123", {
3841
+ * newSecret: "new-secure-secret-key",
3842
+ * gracePeriodHours: 24
3843
+ * });
3844
+ * console.log(result.secretVersion);
3845
+ * console.log(result.previousSecretValidUntil);
3846
+ * ```
3847
+ */
3848
+ rotateSecret(webhookId: string, options: {
3849
+ newSecret: string;
3850
+ gracePeriodHours?: number;
3851
+ }): Promise<RotateSecretResponse>;
3852
+ /**
3853
+ * Get webhook secret rotation status
3854
+ * Check current secret version and grace period status
3855
+ *
3856
+ * @example
3857
+ * ```ts
3858
+ * const status = await client.webhooks.getSecretStatus("webhook_abc123");
3859
+ * console.log(status.secretVersion);
3860
+ * if (status.gracePeriod?.active) {
3861
+ * console.log(`Grace period expires: ${status.gracePeriod.expiresAt}`);
3862
+ * }
3863
+ * ```
3864
+ */
3865
+ getSecretStatus(webhookId: string): Promise<SecretStatusResponse>;
3866
+ /**
3867
+ * Verify webhook signature and replay protection
3868
+ * Validates signature, timestamp, and nonce from received webhook
3869
+ *
3870
+ * @example
3871
+ * ```ts
3872
+ * const isValid = await client.webhooks.verifySignature({
3873
+ * webhookId: "webhook_abc123",
3874
+ * payload: receivedPayload,
3875
+ * signature: headers["x-dealcrawl-signature"],
3876
+ * timestamp: parseInt(headers["x-dealcrawl-timestamp"]),
3877
+ * nonce: headers["x-dealcrawl-nonce"]
3878
+ * });
3879
+ * if (isValid.valid) {
3880
+ * console.log("Webhook is valid");
3881
+ * } else {
3882
+ * console.log(`Invalid: ${isValid.error}`);
3883
+ * }
3884
+ * ```
3885
+ */
3886
+ verifySignature(options: {
3887
+ webhookId: string;
3888
+ payload: Record<string, unknown>;
3889
+ signature: string;
3890
+ timestamp: number;
3891
+ nonce: string;
3892
+ }): Promise<VerifyWebhookResponse>;
3704
3893
  }
3705
3894
 
3706
3895
  /**
package/dist/index.d.ts CHANGED
@@ -690,6 +690,22 @@ interface CancelJobResponse {
690
690
  jobId: string;
691
691
  message: string;
692
692
  }
693
+ /** Job error entry */
694
+ interface JobErrorEntry {
695
+ url?: string;
696
+ error: string;
697
+ timestamp?: string;
698
+ }
699
+ /** Job errors response */
700
+ interface JobErrorsResponse {
701
+ data: JobErrorEntry[];
702
+ pagination: PaginationInfo;
703
+ meta?: {
704
+ jobId: string;
705
+ status?: JobStatus$1;
706
+ offset?: number;
707
+ };
708
+ }
693
709
  /** Job summary in list */
694
710
  interface JobSummary {
695
711
  id: string;
@@ -769,6 +785,32 @@ interface ClientStatsResponse {
769
785
  }>;
770
786
  };
771
787
  }
788
+ /** Full job details response */
789
+ interface JobDetailsResponse {
790
+ id: string;
791
+ type: "scrape" | "crawl" | "dork" | "extract";
792
+ status: JobStatus$1;
793
+ input: Record<string, unknown>;
794
+ result?: unknown;
795
+ progress?: number;
796
+ error?: string;
797
+ dealsFound?: number;
798
+ avgDealScore?: number;
799
+ highestDealScore?: number;
800
+ checkpoint?: CheckpointInfo;
801
+ createdAt: string;
802
+ updatedAt: string;
803
+ completedAt?: string;
804
+ }
805
+ /** Job result response */
806
+ interface JobResultResponse {
807
+ jobId: string;
808
+ status: "completed";
809
+ result: unknown;
810
+ completedAt?: string;
811
+ }
812
+ /** Job export response (string for CSV/Markdown/LLM, object for JSON) */
813
+ type JobExportResponse = string | Record<string, unknown>;
772
814
  /** Webhook in list */
773
815
  interface WebhookItem {
774
816
  id: string;
@@ -824,6 +866,35 @@ interface TestWebhookResponse {
824
866
  responseTime?: number;
825
867
  error?: string;
826
868
  }
869
+ /** Rotate webhook secret response */
870
+ interface RotateSecretResponse {
871
+ success: boolean;
872
+ webhookId: string;
873
+ secretVersion: number;
874
+ previousSecretValidUntil: string;
875
+ gracePeriodHours: number;
876
+ message: string;
877
+ }
878
+ /** Webhook secret status response */
879
+ interface SecretStatusResponse {
880
+ webhookId: string;
881
+ hasSecret: boolean;
882
+ secretVersion: number;
883
+ lastRotatedAt?: string;
884
+ gracePeriod?: {
885
+ active: boolean;
886
+ previousVersion: number;
887
+ expiresAt: string;
888
+ } | null;
889
+ }
890
+ /** Verify webhook signature response */
891
+ interface VerifyWebhookResponse {
892
+ valid: boolean;
893
+ webhookId?: string;
894
+ message: string;
895
+ error?: string;
896
+ reason?: string;
897
+ }
827
898
  /** API key info (safe, without secret) */
828
899
  interface ApiKeyInfo {
829
900
  id: string;
@@ -2645,6 +2716,47 @@ declare class DataResource {
2645
2716
  * ```
2646
2717
  */
2647
2718
  getRecentJobs(limit?: number): Promise<ListJobsResponse>;
2719
+ /**
2720
+ * Get full job details with result
2721
+ *
2722
+ * @example
2723
+ * ```ts
2724
+ * const job = await client.data.getJob("job_abc123");
2725
+ * console.log(job.result);
2726
+ * console.log(job.dealsFound);
2727
+ * ```
2728
+ */
2729
+ getJob(jobId: string): Promise<JobDetailsResponse>;
2730
+ /**
2731
+ * Get only the result of a completed job
2732
+ *
2733
+ * @example
2734
+ * ```ts
2735
+ * const result = await client.data.getJobResult("job_abc123");
2736
+ * console.log(result.result);
2737
+ * ```
2738
+ */
2739
+ getJobResult(jobId: string): Promise<JobResultResponse>;
2740
+ /**
2741
+ * Export job results in various formats
2742
+ * Supports: json, markdown, llm, csv
2743
+ *
2744
+ * @example
2745
+ * ```ts
2746
+ * // Export as JSON
2747
+ * const jsonData = await client.data.exportJob("job_abc123", "json");
2748
+ *
2749
+ * // Export as Markdown
2750
+ * const markdown = await client.data.exportJob("job_abc123", "markdown");
2751
+ *
2752
+ * // Export as LLM-optimized format
2753
+ * const llmText = await client.data.exportJob("job_abc123", "llm");
2754
+ *
2755
+ * // Export as CSV
2756
+ * const csv = await client.data.exportJob("job_abc123", "csv");
2757
+ * ```
2758
+ */
2759
+ exportJob(jobId: string, format?: "json" | "markdown" | "llm" | "csv"): Promise<JobExportResponse>;
2648
2760
  /**
2649
2761
  * List all deals
2650
2762
  *
@@ -3537,6 +3649,24 @@ declare class StatusResource {
3537
3649
  * ```
3538
3650
  */
3539
3651
  getMetrics(jobId: string): Promise<JobMetricsResponse>;
3652
+ /**
3653
+ * Get errors from a job
3654
+ * Useful for debugging large crawls without loading full results
3655
+ *
3656
+ * @example
3657
+ * ```ts
3658
+ * const errors = await client.status.getJobErrors("job_abc123", {
3659
+ * limit: 50,
3660
+ * offset: 0
3661
+ * });
3662
+ * console.log(errors.data);
3663
+ * console.log(errors.pagination.total);
3664
+ * ```
3665
+ */
3666
+ getJobErrors(jobId: string, options?: {
3667
+ limit?: number;
3668
+ offset?: number;
3669
+ }): Promise<JobErrorsResponse>;
3540
3670
  /**
3541
3671
  * Cancel a pending or active job
3542
3672
  *
@@ -3701,6 +3831,65 @@ declare class WebhooksResource {
3701
3831
  * ```
3702
3832
  */
3703
3833
  getByEvent(event: CreateWebhookOptions["event"]): Promise<WebhookItem[]>;
3834
+ /**
3835
+ * Rotate webhook secret
3836
+ * Rotates the secret while keeping the old one valid during grace period
3837
+ *
3838
+ * @example
3839
+ * ```ts
3840
+ * const result = await client.webhooks.rotateSecret("webhook_abc123", {
3841
+ * newSecret: "new-secure-secret-key",
3842
+ * gracePeriodHours: 24
3843
+ * });
3844
+ * console.log(result.secretVersion);
3845
+ * console.log(result.previousSecretValidUntil);
3846
+ * ```
3847
+ */
3848
+ rotateSecret(webhookId: string, options: {
3849
+ newSecret: string;
3850
+ gracePeriodHours?: number;
3851
+ }): Promise<RotateSecretResponse>;
3852
+ /**
3853
+ * Get webhook secret rotation status
3854
+ * Check current secret version and grace period status
3855
+ *
3856
+ * @example
3857
+ * ```ts
3858
+ * const status = await client.webhooks.getSecretStatus("webhook_abc123");
3859
+ * console.log(status.secretVersion);
3860
+ * if (status.gracePeriod?.active) {
3861
+ * console.log(`Grace period expires: ${status.gracePeriod.expiresAt}`);
3862
+ * }
3863
+ * ```
3864
+ */
3865
+ getSecretStatus(webhookId: string): Promise<SecretStatusResponse>;
3866
+ /**
3867
+ * Verify webhook signature and replay protection
3868
+ * Validates signature, timestamp, and nonce from received webhook
3869
+ *
3870
+ * @example
3871
+ * ```ts
3872
+ * const isValid = await client.webhooks.verifySignature({
3873
+ * webhookId: "webhook_abc123",
3874
+ * payload: receivedPayload,
3875
+ * signature: headers["x-dealcrawl-signature"],
3876
+ * timestamp: parseInt(headers["x-dealcrawl-timestamp"]),
3877
+ * nonce: headers["x-dealcrawl-nonce"]
3878
+ * });
3879
+ * if (isValid.valid) {
3880
+ * console.log("Webhook is valid");
3881
+ * } else {
3882
+ * console.log(`Invalid: ${isValid.error}`);
3883
+ * }
3884
+ * ```
3885
+ */
3886
+ verifySignature(options: {
3887
+ webhookId: string;
3888
+ payload: Record<string, unknown>;
3889
+ signature: string;
3890
+ timestamp: number;
3891
+ nonce: string;
3892
+ }): Promise<VerifyWebhookResponse>;
3704
3893
  }
3705
3894
 
3706
3895
  /**
package/dist/index.js CHANGED
@@ -1637,6 +1637,75 @@ var DataResource = class {
1637
1637
  sortOrder: "desc"
1638
1638
  });
1639
1639
  }
1640
+ /**
1641
+ * Get full job details with result
1642
+ *
1643
+ * @example
1644
+ * ```ts
1645
+ * const job = await client.data.getJob("job_abc123");
1646
+ * console.log(job.result);
1647
+ * console.log(job.dealsFound);
1648
+ * ```
1649
+ */
1650
+ async getJob(jobId) {
1651
+ if (!jobId || !jobId.trim()) {
1652
+ throw new Error("jobId is required and cannot be empty");
1653
+ }
1654
+ const result = await get(
1655
+ this.ctx,
1656
+ `/v1/data/jobs/${jobId}`
1657
+ );
1658
+ return result.data;
1659
+ }
1660
+ /**
1661
+ * Get only the result of a completed job
1662
+ *
1663
+ * @example
1664
+ * ```ts
1665
+ * const result = await client.data.getJobResult("job_abc123");
1666
+ * console.log(result.result);
1667
+ * ```
1668
+ */
1669
+ async getJobResult(jobId) {
1670
+ if (!jobId || !jobId.trim()) {
1671
+ throw new Error("jobId is required and cannot be empty");
1672
+ }
1673
+ const result = await get(
1674
+ this.ctx,
1675
+ `/v1/data/jobs/${jobId}/result`
1676
+ );
1677
+ return result.data;
1678
+ }
1679
+ /**
1680
+ * Export job results in various formats
1681
+ * Supports: json, markdown, llm, csv
1682
+ *
1683
+ * @example
1684
+ * ```ts
1685
+ * // Export as JSON
1686
+ * const jsonData = await client.data.exportJob("job_abc123", "json");
1687
+ *
1688
+ * // Export as Markdown
1689
+ * const markdown = await client.data.exportJob("job_abc123", "markdown");
1690
+ *
1691
+ * // Export as LLM-optimized format
1692
+ * const llmText = await client.data.exportJob("job_abc123", "llm");
1693
+ *
1694
+ * // Export as CSV
1695
+ * const csv = await client.data.exportJob("job_abc123", "csv");
1696
+ * ```
1697
+ */
1698
+ async exportJob(jobId, format = "json") {
1699
+ if (!jobId || !jobId.trim()) {
1700
+ throw new Error("jobId is required and cannot be empty");
1701
+ }
1702
+ const result = await get(
1703
+ this.ctx,
1704
+ `/v1/data/jobs/${jobId}/export`,
1705
+ { format }
1706
+ );
1707
+ return result.data;
1708
+ }
1640
1709
  // ============================================
1641
1710
  // DEALS
1642
1711
  // ============================================
@@ -3013,6 +3082,31 @@ var StatusResource = class {
3013
3082
  );
3014
3083
  return result.data;
3015
3084
  }
3085
+ /**
3086
+ * Get errors from a job
3087
+ * Useful for debugging large crawls without loading full results
3088
+ *
3089
+ * @example
3090
+ * ```ts
3091
+ * const errors = await client.status.getJobErrors("job_abc123", {
3092
+ * limit: 50,
3093
+ * offset: 0
3094
+ * });
3095
+ * console.log(errors.data);
3096
+ * console.log(errors.pagination.total);
3097
+ * ```
3098
+ */
3099
+ async getJobErrors(jobId, options) {
3100
+ const result = await get(
3101
+ this.ctx,
3102
+ `/v1/status/${jobId}/errors`,
3103
+ {
3104
+ limit: options?.limit,
3105
+ offset: options?.offset
3106
+ }
3107
+ );
3108
+ return result.data;
3109
+ }
3016
3110
  /**
3017
3111
  * Cancel a pending or active job
3018
3112
  *
@@ -3248,6 +3342,106 @@ var WebhooksResource = class {
3248
3342
  const all = await this.list();
3249
3343
  return all.data.filter((w) => w.event === event);
3250
3344
  }
3345
+ /**
3346
+ * Rotate webhook secret
3347
+ * Rotates the secret while keeping the old one valid during grace period
3348
+ *
3349
+ * @example
3350
+ * ```ts
3351
+ * const result = await client.webhooks.rotateSecret("webhook_abc123", {
3352
+ * newSecret: "new-secure-secret-key",
3353
+ * gracePeriodHours: 24
3354
+ * });
3355
+ * console.log(result.secretVersion);
3356
+ * console.log(result.previousSecretValidUntil);
3357
+ * ```
3358
+ */
3359
+ async rotateSecret(webhookId, options) {
3360
+ if (!webhookId || !webhookId.trim()) {
3361
+ throw new Error("webhookId is required and cannot be empty");
3362
+ }
3363
+ if (!options.newSecret || options.newSecret.length < 16) {
3364
+ throw new Error("newSecret must be at least 16 characters long");
3365
+ }
3366
+ const result = await post(
3367
+ this.ctx,
3368
+ `/v1/webhooks/${webhookId}/rotate`,
3369
+ {
3370
+ newSecret: options.newSecret,
3371
+ gracePeriodHours: options.gracePeriodHours ?? 24
3372
+ }
3373
+ );
3374
+ return result.data;
3375
+ }
3376
+ /**
3377
+ * Get webhook secret rotation status
3378
+ * Check current secret version and grace period status
3379
+ *
3380
+ * @example
3381
+ * ```ts
3382
+ * const status = await client.webhooks.getSecretStatus("webhook_abc123");
3383
+ * console.log(status.secretVersion);
3384
+ * if (status.gracePeriod?.active) {
3385
+ * console.log(`Grace period expires: ${status.gracePeriod.expiresAt}`);
3386
+ * }
3387
+ * ```
3388
+ */
3389
+ async getSecretStatus(webhookId) {
3390
+ if (!webhookId || !webhookId.trim()) {
3391
+ throw new Error("webhookId is required and cannot be empty");
3392
+ }
3393
+ const result = await get(
3394
+ this.ctx,
3395
+ `/v1/webhooks/${webhookId}/secret-status`
3396
+ );
3397
+ return result.data;
3398
+ }
3399
+ /**
3400
+ * Verify webhook signature and replay protection
3401
+ * Validates signature, timestamp, and nonce from received webhook
3402
+ *
3403
+ * @example
3404
+ * ```ts
3405
+ * const isValid = await client.webhooks.verifySignature({
3406
+ * webhookId: "webhook_abc123",
3407
+ * payload: receivedPayload,
3408
+ * signature: headers["x-dealcrawl-signature"],
3409
+ * timestamp: parseInt(headers["x-dealcrawl-timestamp"]),
3410
+ * nonce: headers["x-dealcrawl-nonce"]
3411
+ * });
3412
+ * if (isValid.valid) {
3413
+ * console.log("Webhook is valid");
3414
+ * } else {
3415
+ * console.log(`Invalid: ${isValid.error}`);
3416
+ * }
3417
+ * ```
3418
+ */
3419
+ async verifySignature(options) {
3420
+ if (!options.webhookId || !options.webhookId.trim()) {
3421
+ throw new Error("webhookId is required and cannot be empty");
3422
+ }
3423
+ if (!options.signature) {
3424
+ throw new Error("signature is required");
3425
+ }
3426
+ if (!options.timestamp || options.timestamp <= 0) {
3427
+ throw new Error("timestamp is required and must be positive");
3428
+ }
3429
+ if (!options.nonce || !/^[0-9a-f]{32}$/i.test(options.nonce)) {
3430
+ throw new Error("nonce must be a 32-character hex string");
3431
+ }
3432
+ const result = await post(
3433
+ this.ctx,
3434
+ "/v1/webhooks/verify",
3435
+ {
3436
+ webhookId: options.webhookId,
3437
+ payload: options.payload,
3438
+ signature: options.signature,
3439
+ timestamp: options.timestamp,
3440
+ nonce: options.nonce
3441
+ }
3442
+ );
3443
+ return result.data;
3444
+ }
3251
3445
  };
3252
3446
 
3253
3447
  // src/client.ts