diff --git a/docs/SDKs/deliverable.md b/docs/SDKs/deliverable.md
index d8c2d4dd..e3607d44 100644
--- a/docs/SDKs/deliverable.md
+++ b/docs/SDKs/deliverable.md
@@ -173,7 +173,7 @@ go get github.com/TurboDocx/SDK/packages/go-sdk
com.turbodocx
turbodocx-sdk
- 0.7.0
+ 0.9.0
```
@@ -181,14 +181,14 @@ go get github.com/TurboDocx/SDK/packages/go-sdk
```kotlin
-implementation("com.turbodocx:turbodocx-sdk:0.7.0")
+implementation("com.turbodocx:turbodocx-sdk:0.9.0")
```
```groovy
-implementation 'com.turbodocx:turbodocx-sdk:0.7.0'
+implementation 'com.turbodocx:turbodocx-sdk:0.9.0'
```
@@ -248,7 +248,7 @@ bundle install
- ext-fileinfo
:::tip Modern PHP Features
-This SDK leverages PHP 8.1+ features including enums, named parameters, readonly classes, and match expressions for a superior developer experience.
+This SDK leverages PHP 8.1+ features including enums, named parameters, readonly properties, and match expressions for a superior developer experience.
:::
@@ -364,8 +364,8 @@ use TurboDocx\Config\DeliverableConfig;
// Configure with all options
Deliverable::configure(new DeliverableConfig(
- apiKey: $_ENV['TURBODOCX_API_KEY'], // Required: Your TurboDocx API key
- orgId: $_ENV['TURBODOCX_ORG_ID'], // Required: Your organization ID
+ apiKey: getenv('TURBODOCX_API_KEY') ?: null, // Required: Your TurboDocx API key
+ orgId: getenv('TURBODOCX_ORG_ID') ?: null, // Required: Your organization ID
baseUrl: 'https://api.turbodocx.com' // Optional: Custom API endpoint
));
```
diff --git a/docs/SDKs/index.md b/docs/SDKs/index.md
index acd99a7a..70565a27 100644
--- a/docs/SDKs/index.md
+++ b/docs/SDKs/index.md
@@ -204,7 +204,7 @@ go get github.com/TurboDocx/SDK/packages/go-sdk
com.turbodocx
turbodocx-sdk
- 0.7.0
+ 0.9.0
```
@@ -343,9 +343,9 @@ use TurboDocx\Types\Requests\SendSignatureRequest;
// Configure with your API key
TurboSign::configure(new HttpClientConfig(
- apiKey: $_ENV['TURBODOCX_API_KEY'],
- orgId: $_ENV['TURBODOCX_ORG_ID'],
- senderEmail: $_ENV['TURBODOCX_SENDER_EMAIL']
+ apiKey: getenv('TURBODOCX_API_KEY') ?: null,
+ orgId: getenv('TURBODOCX_ORG_ID') ?: null,
+ senderEmail: getenv('TURBODOCX_SENDER_EMAIL') ?: null
));
// Send a document for signature
diff --git a/docs/SDKs/java.md b/docs/SDKs/java.md
index f2794937..7827f850 100644
--- a/docs/SDKs/java.md
+++ b/docs/SDKs/java.md
@@ -32,7 +32,7 @@ The official TurboDocx SDK for Java applications. Build document generation and
com.turbodocx
turbodocx-sdk
- 0.7.0
+ 0.9.0
```
@@ -40,14 +40,14 @@ The official TurboDocx SDK for Java applications. Build document generation and
```kotlin
-implementation("com.turbodocx:turbodocx-sdk:0.7.0")
+implementation("com.turbodocx:turbodocx-sdk:0.9.0")
```
```groovy
-implementation 'com.turbodocx:turbodocx-sdk:0.7.0'
+implementation 'com.turbodocx:turbodocx-sdk:0.9.0'
```
diff --git a/docs/SDKs/javascript.md b/docs/SDKs/javascript.md
index c167c480..d65dba24 100644
--- a/docs/SDKs/javascript.md
+++ b/docs/SDKs/javascript.md
@@ -106,6 +106,8 @@ Authenticate using `apiKey`. API keys are recommended for server-side applicatio
# .env
TURBODOCX_API_KEY=your_api_key_here
TURBODOCX_ORG_ID=your_org_id_here
+TURBODOCX_SENDER_EMAIL=you@company.com # Required for TurboSign
+TURBODOCX_SENDER_NAME=Your Company Name # Optional (strongly recommended)
```
---
diff --git a/docs/SDKs/partner-java.md b/docs/SDKs/partner-java.md
index 35319961..1173a535 100644
--- a/docs/SDKs/partner-java.md
+++ b/docs/SDKs/partner-java.md
@@ -82,7 +82,7 @@ public class Main {
com.turbodocx
turbodocx-sdk
- 0.7.0
+ 0.9.0
```
@@ -90,14 +90,14 @@ public class Main {
```kotlin
-implementation("com.turbodocx:turbodocx-sdk:0.7.0")
+implementation("com.turbodocx:turbodocx-sdk:0.9.0")
```
```groovy
-implementation 'com.turbodocx:turbodocx-sdk:0.7.0'
+implementation 'com.turbodocx:turbodocx-sdk:0.9.0'
```
diff --git a/docs/SDKs/partner-php.md b/docs/SDKs/partner-php.md
index 1c84249c..98c651e6 100644
--- a/docs/SDKs/partner-php.md
+++ b/docs/SDKs/partner-php.md
@@ -51,8 +51,8 @@ use TurboDocx\Types\Enums\OrgUserRole;
// 1. Configure
TurboPartner::configure(new PartnerClientConfig(
- partnerApiKey: $_ENV['TURBODOCX_PARTNER_API_KEY'],
- partnerId: $_ENV['TURBODOCX_PARTNER_ID'],
+ partnerApiKey: getenv('TURBODOCX_PARTNER_API_KEY') ?: '',
+ partnerId: getenv('TURBODOCX_PARTNER_ID') ?: '',
));
// 2. Create an organization with entitlements
@@ -96,7 +96,7 @@ composer require turbodocx/sdk
- ext-curl
:::tip Modern PHP Features
-This SDK leverages PHP 8.1+ features including enums, named parameters, readonly classes, and match expressions for a superior developer experience.
+This SDK leverages PHP 8.1+ features including enums, named parameters, readonly properties, and match expressions for a superior developer experience.
:::
---
@@ -114,8 +114,8 @@ use TurboDocx\Config\PartnerClientConfig;
// Configure with your partner credentials
TurboPartner::configure(new PartnerClientConfig(
- partnerApiKey: $_ENV['TURBODOCX_PARTNER_API_KEY'], // Required: Must start with TDXP-
- partnerId: $_ENV['TURBODOCX_PARTNER_ID'], // Required: Your partner UUID
+ partnerApiKey: getenv('TURBODOCX_PARTNER_API_KEY') ?: '', // Required: Must start with TDXP-
+ partnerId: getenv('TURBODOCX_PARTNER_ID') ?: '', // Required: Your partner UUID
));
```
diff --git a/docs/SDKs/php.md b/docs/SDKs/php.md
index 50635d75..fa098149 100644
--- a/docs/SDKs/php.md
+++ b/docs/SDKs/php.md
@@ -39,7 +39,7 @@ composer require turbodocx/sdk
- ext-fileinfo
:::tip Modern PHP Features
-This SDK leverages PHP 8.1+ features including enums, named parameters, readonly classes, and match expressions for a superior developer experience.
+This SDK leverages PHP 8.1+ features including enums, named parameters, readonly properties, and match expressions for a superior developer experience.
:::
---
@@ -57,10 +57,10 @@ use TurboDocx\Config\HttpClientConfig;
// Configure with all options
TurboSign::configure(new HttpClientConfig(
- apiKey: $_ENV['TURBODOCX_API_KEY'], // Required: Your TurboDocx API key
- orgId: $_ENV['TURBODOCX_ORG_ID'], // Required: Your organization ID
- senderEmail: $_ENV['TURBODOCX_SENDER_EMAIL'], // Required: Reply-to email for signature requests
- senderName: $_ENV['TURBODOCX_SENDER_NAME'], // Optional: Sender name (strongly recommended)
+ apiKey: getenv('TURBODOCX_API_KEY') ?: null, // Required: Your TurboDocx API key
+ orgId: getenv('TURBODOCX_ORG_ID') ?: null, // Required: Your organization ID
+ senderEmail: getenv('TURBODOCX_SENDER_EMAIL') ?: null, // Required: Reply-to email for signature requests
+ senderName: getenv('TURBODOCX_SENDER_NAME') ?: null, // Optional: Sender name (strongly recommended)
baseUrl: 'https://api.turbodocx.com' // Optional: Custom API endpoint
));
```
diff --git a/docs/SDKs/quote.md b/docs/SDKs/quote.md
index 7dc5713b..90321558 100644
--- a/docs/SDKs/quote.md
+++ b/docs/SDKs/quote.md
@@ -234,7 +234,7 @@ import turbodocx "github.com/TurboDocx/SDK/packages/go-sdk"
com.turbodocx
turbodocx-sdk
- 0.7.0
+ 0.9.0
```
@@ -242,14 +242,14 @@ import turbodocx "github.com/TurboDocx/SDK/packages/go-sdk"
```groovy
-implementation 'com.turbodocx:turbodocx-sdk:0.7.0'
+implementation 'com.turbodocx:turbodocx-sdk:0.9.0'
```
```kotlin
-implementation("com.turbodocx:turbodocx-sdk:0.7.0")
+implementation("com.turbodocx:turbodocx-sdk:0.9.0")
```
@@ -414,8 +414,8 @@ use TurboDocx\TurboQuote;
use TurboDocx\Config\QuoteClientConfig;
TurboQuote::configure(new QuoteClientConfig(
- apiKey: $_ENV['TURBODOCX_API_KEY'],
- orgId: $_ENV['TURBODOCX_ORG_ID'], // optional, but required for most endpoints
+ apiKey: getenv('TURBODOCX_API_KEY') ?: null,
+ orgId: getenv('TURBODOCX_ORG_ID') ?: null, // optional, but required for most endpoints
));
```
diff --git a/docs/SDKs/turbosign.md b/docs/SDKs/turbosign.md
index 57bec31a..e6213914 100644
--- a/docs/SDKs/turbosign.md
+++ b/docs/SDKs/turbosign.md
@@ -179,7 +179,7 @@ go get github.com/TurboDocx/SDK/packages/go-sdk
com.turbodocx
turbodocx-sdk
- 0.7.0
+ 0.9.0
```
@@ -187,14 +187,14 @@ go get github.com/TurboDocx/SDK/packages/go-sdk
```kotlin
-implementation("com.turbodocx:turbodocx-sdk:0.7.0")
+implementation("com.turbodocx:turbodocx-sdk:0.9.0")
```
```groovy
-implementation 'com.turbodocx:turbodocx-sdk:0.7.0'
+implementation 'com.turbodocx:turbodocx-sdk:0.9.0'
```
@@ -254,7 +254,7 @@ bundle install
- ext-fileinfo
:::tip Modern PHP Features
-This SDK leverages PHP 8.1+ features including enums, named parameters, readonly classes, and match expressions for a superior developer experience.
+This SDK leverages PHP 8.1+ features including enums, named parameters, readonly properties, and match expressions for a superior developer experience.
:::
@@ -361,10 +361,10 @@ use TurboDocx\Config\HttpClientConfig;
// Configure with all options
TurboSign::configure(new HttpClientConfig(
- apiKey: $_ENV['TURBODOCX_API_KEY'], // Required: Your TurboDocx API key
- orgId: $_ENV['TURBODOCX_ORG_ID'], // Required: Your organization ID
- senderEmail: $_ENV['TURBODOCX_SENDER_EMAIL'], // Required: Reply-to email for signature requests
- senderName: $_ENV['TURBODOCX_SENDER_NAME'], // Optional: Sender name (strongly recommended)
+ apiKey: getenv('TURBODOCX_API_KEY') ?: null, // Required: Your TurboDocx API key
+ orgId: getenv('TURBODOCX_ORG_ID') ?: null, // Required: Your organization ID
+ senderEmail: getenv('TURBODOCX_SENDER_EMAIL') ?: null, // Required: Reply-to email for signature requests
+ senderName: getenv('TURBODOCX_SENDER_NAME') ?: null, // Optional: Sender name (strongly recommended)
baseUrl: 'https://api.turbodocx.com' // Optional: Custom API endpoint
));
```
@@ -547,6 +547,8 @@ Creating a client per request leaks OkHttp threads until they are closed. Prefer
# .env
TURBODOCX_API_KEY=your_api_key_here
TURBODOCX_ORG_ID=your_org_id_here
+TURBODOCX_SENDER_EMAIL=you@company.com # Required for TurboSign
+TURBODOCX_SENDER_NAME=Your Company Name # Optional (strongly recommended)
```
@@ -2965,6 +2967,175 @@ puts result["documentId"]
+### Send without emails and sign in your app {#send-without-emails}
+
+Set `sendEmail` to `false` to create the signature request without emailing the signers their links. The document still goes out for signing, and passcode and completed-copy emails are still sent. The opt-out also covers the initial CC email, later signers' turn emails and scheduled reminders. Then call `createSigningUrl` for a signer when they are ready to sign in your app. For a signer with an email/SMS passcode or no verification (as here), the URL is their reusable signing link for the life of the document, so treat it like a password and don't log it. `external_idv` and `override` signers get a single-use URL that expires in about five minutes; mint those at the moment of signing.
+
+:::info Prerequisite
+Embedded signing must be turned on for your organization: in the E-Signature settings, turn on **Enable identity verification** (see [Set up your organization](/docs/TurboSign/how-to-enable-embedded-signing)). While it is off, `createSigningUrl` fails with `EmbeddedSigningNotEnabled`.
+:::
+
+
+
+
+```typescript
+const result = await TurboSign.sendSignature({
+ fileLink: "https://www.turbodocx.com/examples/turbodocx.pdf",
+ documentName: "Service Agreement",
+ recipients: [{ name: "Recipient Name", email: "recipient@example.com", signingOrder: 1 }],
+ fields: [{ type: "signature", page: 1, x: 100, y: 500, width: 200, height: 50, recipientEmail: "recipient@example.com" }],
+ sendEmail: false, // no signing-link emails; you hand out the link
+});
+
+// When the signer is ready, mint their signing URL
+const link = await TurboSign.createSigningUrl(result.documentId, {
+ recipientId: result.recipients[0].id,
+});
+console.log(link.url); // open in a new tab or redirect to it
+```
+
+
+
+
+```python
+result = await TurboSign.send_signature(
+ recipients=[{"name": "Recipient Name", "email": "recipient@example.com", "signingOrder": 1}],
+ fields=[{"type": "signature", "page": 1, "x": 100, "y": 500, "width": 200, "height": 50, "recipientEmail": "recipient@example.com"}],
+ file_link="https://www.turbodocx.com/examples/turbodocx.pdf",
+ document_name="Service Agreement",
+ send_email=False, # no signing-link emails; you hand out the link
+)
+
+# When the signer is ready, mint their signing URL
+link = await TurboSign.create_signing_url(
+ result["documentId"],
+ recipient_id=result["recipients"][0]["id"],
+)
+print(link["url"]) # open in a new tab or redirect to it
+```
+
+
+
+
+```php
+use TurboDocx\TurboSign;
+use TurboDocx\Types\Recipient;
+use TurboDocx\Types\Field;
+use TurboDocx\Types\SignatureFieldType;
+use TurboDocx\Types\Requests\SendSignatureRequest;
+use TurboDocx\Types\Requests\CreateSigningUrlRequest;
+
+$result = TurboSign::sendSignature(
+ new SendSignatureRequest(
+ recipients: [new Recipient('Recipient Name', 'recipient@example.com', 1)],
+ fields: [
+ new Field(
+ type: SignatureFieldType::SIGNATURE,
+ recipientEmail: 'recipient@example.com',
+ page: 1,
+ x: 100,
+ y: 500,
+ width: 200,
+ height: 50
+ )
+ ],
+ fileLink: 'https://www.turbodocx.com/examples/turbodocx.pdf',
+ documentName: 'Service Agreement',
+ sendEmail: false // no signing-link emails; you hand out the link
+ )
+);
+
+// When the signer is ready, mint their signing URL
+$link = TurboSign::createSigningUrl(
+ $result->documentId,
+ new CreateSigningUrlRequest(recipientId: $result->recipients[0]['id'])
+);
+echo $link->url; // open in a new tab or redirect to it
+```
+
+
+
+
+```go
+sendEmail := false // no signing-link emails; you hand out the link
+result, err := client.TurboSign.SendSignature(ctx, &turbodocx.SendSignatureRequest{
+ FileLink: "https://www.turbodocx.com/examples/turbodocx.pdf",
+ DocumentName: "Service Agreement",
+ Recipients: []turbodocx.Recipient{
+ {Name: "Recipient Name", Email: "recipient@example.com", SigningOrder: 1},
+ },
+ Fields: []turbodocx.Field{
+ {Type: "signature", Page: 1, X: 100, Y: 500, Width: 200, Height: 50, RecipientEmail: "recipient@example.com"},
+ },
+ SendEmail: &sendEmail,
+})
+if err != nil {
+ log.Fatal(err)
+}
+
+// When the signer is ready, mint their signing URL
+link, err := client.TurboSign.CreateSigningURL(ctx, result.DocumentID, &turbodocx.CreateSigningURLRequest{
+ RecipientID: result.Recipients[0].ID,
+})
+if err != nil {
+ log.Fatal(err)
+}
+fmt.Println(link.URL) // open in a new tab or redirect to it
+```
+
+
+
+
+```java
+SendSignatureResponse result = client.turboSign().sendSignature(
+ new SendSignatureRequest.Builder()
+ .fileLink("https://www.turbodocx.com/examples/turbodocx.pdf")
+ .documentName("Service Agreement")
+ .recipients(Arrays.asList(
+ new Recipient("Recipient Name", "recipient@example.com", 1)
+ ))
+ .fields(Arrays.asList(
+ new Field("signature", 1, 100, 500, 200, 50, "recipient@example.com")
+ ))
+ .sendEmail(false) // no signing-link emails; you hand out the link
+ .build()
+);
+
+// When the signer is ready, mint their signing URL
+CreateSigningUrlResponse link = client.turboSign().createSigningUrl(
+ result.getDocumentId(),
+ new CreateSigningUrlRequest.Builder()
+ .recipientId(result.getRecipients().get(0).getId())
+ .build()
+);
+System.out.println(link.getUrl()); // open in a new tab or redirect to it
+```
+
+
+
+
+```ruby
+result = TurboDocxSdk::TurboSign.send_signature(
+ fileLink: "https://www.turbodocx.com/examples/turbodocx.pdf",
+ documentName: "Service Agreement",
+ recipients: [{ name: "Recipient Name", email: "recipient@example.com", signingOrder: 1 }],
+ fields: [{ type: "signature", page: 1, x: 100, y: 500, width: 200, height: 50, recipientEmail: "recipient@example.com" }],
+ sendEmail: false # no signing-link emails; you hand out the link
+)
+
+# When the signer is ready, mint their signing URL
+link = TurboDocxSdk::TurboSign.create_signing_url(
+ result["documentId"],
+ recipient_id: result["recipients"][0]["id"]
+)
+puts link["url"] # open in a new tab or redirect to it
+```
+
+
+
+
+To frame the signing page inside your app instead of opening it, and for identity verification options, see [Embedded Signing](/docs/TurboSign/embedded-signing).
+
### Reminders and expiration {#reminders-and-expiration}
@@ -4995,19 +5166,23 @@ enum ConditionalAction: string {
-### Readonly Classes {#readonly-classes}
+### Typed Classes {#readonly-classes}
-The SDK uses readonly classes with typed properties:
+The SDK uses final classes with typed, constructor-promoted properties:
```php
final class Recipient {
public function __construct(
public string $name,
public string $email,
- public int $signingOrder
+ public ?int $signingOrder = null, // Required unless the recipient has a role
+ public ?string $phone = null, // E.164; required for SMS passcodes
+ public ?string $externalId = null, // Your own key, unique in the document
+ public ?IdentityVerification $identityVerification = null,
+ public ?string $role = null // Template signer role this recipient fills
) {}
}
@@ -5024,7 +5199,7 @@ final class Field {
public ?string $defaultValue = null, // checkbox: 'true'/'false'; date: a fixed MM/DD/YYYY (omit to auto-fill the signing date)
public bool $isMultiline = false,
public bool $isReadonly = false,
- public bool $required = false,
+ public ?bool $required = null, // null (default) = required; false = optional for the signer
public ?string $backgroundColor = null,
public ?FieldMetadata $metadata = null // Conditional (IF/THEN) metadata
) {}
diff --git a/docs/SDKs/webhooks.md b/docs/SDKs/webhooks.md
index 30bbbaeb..d9c100ee 100644
--- a/docs/SDKs/webhooks.md
+++ b/docs/SDKs/webhooks.md
@@ -213,7 +213,7 @@ import turbodocx "github.com/TurboDocx/SDK/packages/go-sdk"
com.turbodocx
turbodocx-sdk
- 0.7.0
+ 0.9.0
```
@@ -221,14 +221,14 @@ import turbodocx "github.com/TurboDocx/SDK/packages/go-sdk"
```groovy
-implementation 'com.turbodocx:turbodocx-sdk:0.7.0'
+implementation 'com.turbodocx:turbodocx-sdk:0.9.0'
```
```kotlin
-implementation("com.turbodocx:turbodocx-sdk:0.7.0")
+implementation("com.turbodocx:turbodocx-sdk:0.9.0")
```
@@ -348,8 +348,8 @@ TurboWebhooks.configure(
use TurboDocx\TurboWebhooks;
TurboWebhooks::configureFromCredentials(
- apiKey: $_ENV['TURBODOCX_API_KEY'],
- orgId: $_ENV['TURBODOCX_ORG_ID'],
+ apiKey: getenv('TURBODOCX_API_KEY') ?: '', // string params: '' makes an unset variable a clear auth error
+ orgId: getenv('TURBODOCX_ORG_ID') ?: '',
);
```
@@ -369,8 +369,8 @@ If you need to build the config yourself, pass `skipSenderValidation: true` —
use TurboDocx\Config\HttpClientConfig;
TurboWebhooks::configure(new HttpClientConfig(
- apiKey: $_ENV['TURBODOCX_API_KEY'],
- orgId: $_ENV['TURBODOCX_ORG_ID'],
+ apiKey: getenv('TURBODOCX_API_KEY') ?: null,
+ orgId: getenv('TURBODOCX_ORG_ID') ?: null,
skipSenderValidation: true, // webhooks don't send email
));
```
@@ -1189,8 +1189,8 @@ use TurboDocx\Exceptions\ValidationException;
use TurboDocx\Types\Enums\WebhookEvent;
TurboWebhooks::configureFromCredentials(
- apiKey: $_ENV['TURBODOCX_API_KEY'],
- orgId: $_ENV['TURBODOCX_ORG_ID'],
+ apiKey: getenv('TURBODOCX_API_KEY') ?: '', // string params: '' makes an unset variable a clear auth error
+ orgId: getenv('TURBODOCX_ORG_ID') ?: '',
);
try {
@@ -1531,7 +1531,7 @@ use function TurboDocx\Utils\verifyWebhookSignature;
$rawBody = file_get_contents('php://input'); // raw bytes — do NOT json_decode first
$signatureHeader = $_SERVER['HTTP_X_TURBODOCX_SIGNATURE'] ?? '';
$timestampHeader = $_SERVER['HTTP_X_TURBODOCX_TIMESTAMP'] ?? '';
-$secret = $_ENV['TURBODOCX_WEBHOOK_SECRET'];
+$secret = getenv('TURBODOCX_WEBHOOK_SECRET') ?: '';
if (!verifyWebhookSignature($rawBody, $signatureHeader, $timestampHeader, $secret)) {
http_response_code(401);
@@ -2952,7 +2952,7 @@ ok := turbodocx.VerifyWebhookSignature(
)
```
-Pass `&turbodocx.VerifyWebhookSignatureOptions{ToleranceSeconds: 60}` to tighten the window, or `ToleranceSeconds: -1` to disable the timestamp check entirely (NOT recommended in production).
+Pass `&turbodocx.VerifyWebhookSignatureOptions{ToleranceSeconds: 60}` to tighten the window, or a larger positive value to widen it. `0` (the zero value) means the 300-second default, so the Go helper has no way to turn the timestamp check off, and a negative value rejects every delivery.
diff --git a/static/scripts/webhooks/verification/php.php b/static/scripts/webhooks/verification/php.php
index d7118fb0..d330f978 100644
--- a/static/scripts/webhooks/verification/php.php
+++ b/static/scripts/webhooks/verification/php.php
@@ -185,7 +185,7 @@ function testWebhookVerification(string $secret = 'test-secret'): bool
// Example usage when script is run directly
if (__FILE__ === $_SERVER['SCRIPT_FILENAME']) {
- $webhookSecret = $_ENV['WEBHOOK_SECRET'] ?? 'your-webhook-secret';
+ $webhookSecret = getenv('WEBHOOK_SECRET') ?: 'your-webhook-secret';
// If running from command line, run test
if (php_sapi_name() === 'cli') {