LoopFour

Webhooks API

Receive and process webhook events from external providers

Webhooks API

Receive webhook events from external providers to trigger workflows. Webhooks are automatically verified, stored for audit, and routed to matching workflows.

Webhook endpoints do not require API key authentication - they use provider-specific signature verification instead.

Provider Webhook Endpoints

Each provider has a dedicated endpoint that handles signature verification and event routing.

ProviderEndpointSignature Header
StripePOST /webhooks/stripe/:companyIdstripe-signature
HubSpotPOST /webhooks/hubspot/:companyIdx-hubspot-signature-v3
QuickBooksPOST /webhooks/quickbooks/:companyIdintuit-signature
SalesforcePOST /webhooks/salesforce/:companyIdN/A (SOAP XML)
PandaDocPOST /webhooks/pandadoc/:companyIdx-pandadoc-signature
NetSuitePOST /webhooks/netsuite/:companyIdx-netsuite-signature
GmailPOST /webhooks/gmail/:companyIdN/A (Pub/Sub)
CustomPOST /webhooks/custom/:companyId/:pathN/A

Path Parameters

ParameterTypeDescription
companyIdstringYour company UUID
pathstringCustom path for routing (custom webhooks only)

Standard Response

All webhook endpoints return the same response format:

{
  "received": true,
  "eventId": "evt_550e8400-e29b-41d4-a716-446655440000",
  "workflowsTriggered": 2
}
FieldTypeDescription
receivedbooleanAlways true on success
eventIdstringInternal event ID for tracking
workflowsTriggerednumberNumber of workflows triggered

Stripe Webhooks

Receive events from Stripe for payment and subscription events.

POST /webhooks/stripe/:companyId

Headers

HeaderRequiredDescription
stripe-signatureYesStripe webhook signature
Content-TypeYesapplication/json

Common Event Types

EventDescription
invoice.paidInvoice was paid
invoice.payment_failedPayment attempt failed
customer.subscription.createdNew subscription
customer.subscription.updatedSubscription changed
customer.subscription.deletedSubscription cancelled
payment_intent.succeededPayment completed
payment_intent.payment_failedPayment failed
charge.refundedCharge was refunded

Example Payload

{
  "id": "evt_1234567890",
  "type": "invoice.paid",
  "data": {
    "object": {
      "id": "in_1234567890",
      "customer": "cus_xxx",
      "amount_paid": 9900,
      "currency": "usd",
      "customer_email": "customer@example.com",
      "status": "paid"
    }
  },
  "created": 1705312000
}

Stripe Configuration

  1. Go to Stripe Dashboard → Developers → Webhooks
  2. Add endpoint: https://your-api.com/webhooks/stripe/{companyId}
  3. Select events to receive
  4. Copy the signing secret to your environment

HubSpot Webhooks

Receive CRM and marketing events from HubSpot.

POST /webhooks/hubspot/:companyId

Headers

HeaderRequiredDescription
x-hubspot-signature-v3YesHubSpot v3 signature
Content-TypeYesapplication/json

Common Event Types

EventDescription
contact.creationNew contact created
contact.propertyChangeContact property updated
contact.deletionContact deleted
deal.creationNew deal created
deal.propertyChangeDeal property updated
company.creationNew company created

Example Payload

[
  {
    "eventId": 123456789,
    "subscriptionId": 12345,
    "portalId": 12345678,
    "occurredAt": 1705312000000,
    "subscriptionType": "contact.creation",
    "attemptNumber": 0,
    "objectId": 12345,
    "changeSource": "CRM",
    "propertyName": "email",
    "propertyValue": "new@example.com"
  }
]

HubSpot Configuration

  1. Go to HubSpot Settings → Integrations → Private Apps
  2. Create or edit your app
  3. Go to Webhooks tab
  4. Add subscription URL: https://your-api.com/webhooks/hubspot/{companyId}
  5. Select object types and events

QuickBooks Webhooks

Receive accounting events from QuickBooks Online.

POST /webhooks/quickbooks/:companyId

Headers

HeaderRequiredDescription
intuit-signatureYesIntuit webhook signature
Content-TypeYesapplication/json

Common Event Types

EventDescription
CustomerCustomer created/updated/deleted
InvoiceInvoice created/updated/deleted
PaymentPayment recorded
BillBill created/updated
VendorVendor created/updated
AccountAccount created/updated

Example Payload

{
  "eventNotifications": [
    {
      "realmId": "1234567890",
      "dataChangeEvent": {
        "entities": [
          {
            "name": "Invoice",
            "id": "123",
            "operation": "Create",
            "lastUpdated": "2024-01-15T10:30:00.000Z"
          }
        ]
      }
    }
  ]
}

QuickBooks Configuration

  1. Go to Intuit Developer Portal → My Apps
  2. Select your app → Webhooks
  3. Add endpoint: https://your-api.com/webhooks/quickbooks/{companyId}
  4. Select entities to subscribe to
  5. Copy the verifier token to your environment

Salesforce Webhooks

Receive events from Salesforce via outbound messages or Platform Events.

POST /webhooks/salesforce/:companyId

Content Types

Content-TypeFormatDescription
text/xmlSOAP XMLOutbound messages from Workflow Rules
application/xmlSOAP XMLOutbound messages (alternate)
application/jsonJSONPlatform Events, Change Data Capture

SOAP XML Response

For outbound messages, Salesforce expects a SOAP acknowledgment:

<?xml version="1.0" encoding="UTF-8"?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:out="http://soap.sforce.com/2005/09/outbound">
  <soapenv:Body>
    <out:notificationsResponse>
      <out:Ack>true</out:Ack>
    </out:notificationsResponse>
  </soapenv:Body>
</soapenv:Envelope>

Common Event Types

EventDescription
AccountAccount record changes
ContactContact record changes
OpportunityOpportunity record changes
LeadLead record changes
CaseCase record changes
Custom objectsAny custom object changes

Example JSON Payload (Change Data Capture)

{
  "schema": "xxx",
  "payload": {
    "ChangeEventHeader": {
      "entityName": "Account",
      "recordIds": ["001xxx"],
      "changeType": "UPDATE",
      "changedFields": ["Name", "Industry"]
    },
    "Name": "Acme Corp",
    "Industry": "Technology"
  }
}

Salesforce Configuration

For Outbound Messages:

  1. Setup → Workflow Rules → Create new rule
  2. Add Outbound Message action
  3. Set endpoint URL: https://your-api.com/webhooks/salesforce/{companyId}

For Change Data Capture:

  1. Setup → Change Data Capture
  2. Select objects to track
  3. Configure external client with endpoint

PandaDoc Webhooks

Receive document lifecycle events from PandaDoc.

POST /webhooks/pandadoc/:companyId

Headers

HeaderRequiredDescription
x-pandadoc-signatureYesPandaDoc webhook signature
Content-TypeYesapplication/json

Common Event Types

EventDescription
document_state_changedDocument status changed
recipient_completedRecipient signed/completed
document_completedAll recipients completed
document_paidDocument payment received
document_viewedDocument was viewed
document_deletedDocument was deleted

Example Payload

{
  "event": "document_state_changed",
  "data": {
    "id": "doc_xxx",
    "name": "Sales Contract",
    "status": "document.completed",
    "date_created": "2024-01-15T10:00:00.000Z",
    "date_modified": "2024-01-15T10:30:00.000Z",
    "recipients": [
      {
        "email": "signer@example.com",
        "completed": true,
        "completed_at": "2024-01-15T10:30:00.000Z"
      }
    ]
  }
}

PandaDoc Configuration

  1. Go to PandaDoc Settings → Integrations → Webhooks
  2. Add webhook URL: https://your-api.com/webhooks/pandadoc/{companyId}
  3. Select events to receive
  4. Copy the shared key for signature verification

NetSuite Webhooks

Receive events from NetSuite via RESTlet or SuiteScript.

POST /webhooks/netsuite/:companyId

Headers

HeaderRequiredDescription
x-netsuite-signatureNoOptional signature for verification
Content-TypeYesapplication/json

Common Event Types

EventDescription
record.createRecord created
record.updateRecord updated
record.deleteRecord deleted
scheduled.scriptScheduled script event

Example Payload

{
  "type": "record.create",
  "recordType": "invoice",
  "recordId": "12345",
  "data": {
    "entity": "123",
    "trandate": "2024-01-15",
    "total": 1500.00,
    "status": "Open"
  },
  "timestamp": "2024-01-15T10:30:00.000Z"
}

NetSuite Configuration

  1. Create a RESTlet or SuiteScript that POSTs to your webhook endpoint
  2. Configure User Event Scripts to trigger on record changes
  3. Set up a Scheduled Script for periodic events

Gmail Webhooks

Receive email notifications via Google Cloud Pub/Sub.

POST /webhooks/gmail/:companyId

Gmail uses Google Cloud Pub/Sub for push notifications. You need to configure a Pub/Sub topic and subscription.

Pub/Sub Message Format

{
  "message": {
    "data": "eyJlbWFpbEFkZHJlc3MiOiJ1c2VyQGV4YW1wbGUuY29tIiwiaGlzdG9yeUlkIjoiMTIzNDU2Nzg5MCJ9",
    "messageId": "123456789",
    "publishTime": "2024-01-15T10:30:00.000Z"
  },
  "subscription": "projects/PROJECT/subscriptions/SUB"
}

The data field is base64-encoded JSON containing:

{
  "emailAddress": "user@example.com",
  "historyId": "1234567890"
}

Gmail Configuration

  1. Enable Gmail API in Google Cloud Console
  2. Create a Pub/Sub topic
  3. Create a push subscription with endpoint: https://your-api.com/webhooks/gmail/{companyId}
  4. Call users.watch() to start receiving notifications

Custom Webhooks

Receive events from any external system with a flexible endpoint.

POST /webhooks/custom/:companyId/:path

Path Parameters

ParameterTypeDescription
companyIdstringYour company UUID
pathstringCustom path used as event type

Example

curl -X POST "https://your-api.com/webhooks/custom/00000000-0000-0000-0000-000000000001/order-received" \
  -H "Content-Type: application/json" \
  -d '{
    "orderId": "12345",
    "customer": "cust_xxx",
    "total": 99.99
  }'

The path parameter becomes the event type for workflow matching. In the example above, workflows with trigger webhook.custom.order-received would be triggered.

Non-JSON Payloads

Custom webhooks accept any content type. Non-JSON payloads are wrapped:

{
  "rawBody": "your raw content here"
}

System Webhooks

These endpoints are used by internal services and typically don't need manual configuration.

Nango Webhooks

POST /webhooks/nango

Receives OAuth completion and sync events from Nango.

HeaderDescription
x-nango-signatureHMAC-SHA256 signature

Event Types:

TypeDescription
authOAuth flow completed (success or failure)
syncData sync completed
webhookForwarded provider webhook

Trigger.dev Webhooks

POST /webhooks/trigger

Receives run status updates from Trigger.dev.

HeaderDescription
x-trigger-signatureHMAC-SHA256 signature

Event Types:

TypeDescription
run.completedWorkflow run completed successfully
run.failedWorkflow run failed
run.cancelledWorkflow run was cancelled

Webhook Events

All incoming webhooks are stored in the webhook_events table for audit and replay.

Event Structure

{
  "id": "evt_550e8400-e29b-41d4-a716-446655440000",
  "companyId": "00000000-0000-0000-0000-000000000001",
  "provider": "stripe",
  "eventType": "invoice.paid",
  "eventId": "evt_external_123",
  "payload": { ... },
  "signatureVerified": "verified",
  "status": "processed",
  "workflowsTriggered": [
    { "workflowId": "wf_xxx", "runId": "run_xxx" }
  ],
  "createdAt": "2024-01-15T10:30:00.000Z"
}

Signature Verification Status

StatusDescription
pendingVerification not yet attempted
verifiedSignature verified successfully
failedSignature verification failed
skippedNo signature provided/required

Event Processing Status

StatusDescription
receivedEvent received and stored
processingCurrently triggering workflows
processedAll workflows triggered successfully
failedProcessing failed
ignoredNo matching workflows found

Signature Verification

How Verification Works

  1. Provider sends webhook with signature header
  2. API verifies signature using stored webhook secret
  3. If verification fails, returns 401 Unauthorized
  4. If verification succeeds, processes the event

Stripe Signature Verification

const crypto = require('crypto');
 
function verifyStripeSignature(payload, signature, secret) {
  const elements = signature.split(',');
  const timestamp = elements.find(e => e.startsWith('t='))?.split('=')[1];
  const v1Signature = elements.find(e => e.startsWith('v1='))?.split('=')[1];
 
  const signedPayload = `${timestamp}.${payload}`;
  const expectedSignature = crypto
    .createHmac('sha256', secret)
    .update(signedPayload)
    .digest('hex');
 
  return v1Signature === expectedSignature;
}

HubSpot Signature Verification (v3)

const crypto = require('crypto');
 
function verifyHubSpotSignature(payload, signature, secret, method, url, timestamp) {
  const sourceString = `${method}${url}${payload}${timestamp}`;
  const expectedSignature = crypto
    .createHmac('sha256', secret)
    .update(sourceString)
    .digest('base64');
 
  return signature === expectedSignature;
}

QuickBooks Signature Verification

const crypto = require('crypto');
 
function verifyQuickBooksSignature(payload, signature, verifierToken) {
  const expectedSignature = crypto
    .createHmac('sha256', verifierToken)
    .update(payload)
    .digest('base64');
 
  return signature === expectedSignature;
}

Error Responses

Invalid Signature (401)

{
  "error": "Invalid signature"
}

Invalid JSON (400)

{
  "error": "Invalid JSON"
}

Invalid Payload (400)

{
  "error": "Invalid payload"
}

Invalid Pub/Sub Message (400)

{
  "error": "Invalid Pub/Sub message format"
}

Rate Limiting

Webhook endpoints have a higher rate limit than standard API endpoints:

CategoryLimit
Webhooks10,000 requests/minute per IP

When rate limited:

{
  "success": false,
  "error": {
    "code": "RATE_LIMITED",
    "message": "Rate limit exceeded"
  }
}

Outbound Webhooks

Register webhook endpoints to receive notifications when workflow execution status changes.

Outbound webhooks notify your systems about workflow events. This is the inverse of inbound webhooks which trigger workflows.

Webhook Endpoint Management

MethodEndpointDescription
GET/api/v1/webhook-endpointsList webhook endpoints
POST/api/v1/webhook-endpointsCreate webhook endpoint
GET/api/v1/webhook-endpoints/:idGet endpoint details
PUT/api/v1/webhook-endpoints/:idUpdate endpoint
DELETE/api/v1/webhook-endpoints/:idDelete endpoint
POST/api/v1/webhook-endpoints/:id/rotate-secretRotate signing secret
POST/api/v1/webhook-endpoints/:id/testSend test webhook
GET/api/v1/webhook-endpoints/:id/deliveriesList delivery attempts
POST/api/v1/webhook-endpoints/:id/deliveries/:deliveryId/retryRetry failed delivery

Create Webhook Endpoint

POST /api/v1/webhook-endpoints

Request Body

FieldTypeRequiredDescription
namestringYesEndpoint name
urlstringYesWebhook URL (HTTPS recommended)
eventsstring[]NoEvent types to receive (null = all events)
workflowIdsstring[]NoFilter to specific workflows (null = all)
headersobjectNoCustom headers to include
descriptionstringNoDescription

Example Request

{
  "name": "Invoice System",
  "url": "https://api.example.com/webhooks/workflows",
  "events": ["workflow.completed", "workflow.failed"],
  "workflowIds": ["550e8400-e29b-41d4-a716-446655440000"],
  "headers": {
    "Authorization": "Bearer your-token"
  },
  "description": "Notifies invoice system when workflows complete"
}

Response (201 Created)

{
  "success": true,
  "data": {
    "id": "whe_550e8400-e29b-41d4-a716-446655440000",
    "name": "Invoice System",
    "url": "https://api.example.com/webhooks/workflows",
    "secret": "whsec_abc123...",
    "events": ["workflow.completed", "workflow.failed"],
    "workflowIds": ["550e8400-e29b-41d4-a716-446655440000"],
    "headers": {"Authorization": "Bearer your-token"},
    "status": "active",
    "createdAt": "2024-01-15T10:00:00.000Z"
  }
}

The secret is only returned on creation. Store it securely for signature verification.

Webhook Event Types

EventDescription
workflow.startedWorkflow execution began
workflow.completedWorkflow completed successfully
workflow.failedWorkflow failed with error
workflow.timeoutWorkflow exceeded timeout
workflow.cancelledWorkflow was cancelled
workflow.checkpointLong-running workflow checkpoint

Webhook Payload Format

{
  "id": "evt_550e8400-e29b-41d4-a716-446655440000",
  "type": "workflow.completed",
  "timestamp": "2024-01-15T10:30:00.000Z",
  "workflowId": "550e8400-e29b-41d4-a716-446655440000",
  "workflowName": "Invoice Processing",
  "runId": "run_660e8400-e29b-41d4-a716-446655440001",
  "companyId": "00000000-0000-0000-0000-000000000001",
  "status": "completed",
  "triggerType": "webhook",
  "startedAt": "2024-01-15T10:29:55.000Z",
  "completedAt": "2024-01-15T10:30:00.000Z",
  "durationMs": 5000,
  "input": {
    "invoiceId": "inv_123"
  },
  "output": {
    "processed": true,
    "slackMessageId": "1234567890.123456"
  }
}

Failed Workflow Payload

{
  "id": "evt_770e8400-e29b-41d4-a716-446655440002",
  "type": "workflow.failed",
  "timestamp": "2024-01-15T10:30:00.000Z",
  "workflowId": "550e8400-e29b-41d4-a716-446655440000",
  "workflowName": "Invoice Processing",
  "runId": "run_880e8400-e29b-41d4-a716-446655440003",
  "status": "failed",
  "durationMs": 3000,
  "error": {
    "code": "STEP_ERROR",
    "message": "API rate limit exceeded",
    "stepId": "sync-to-crm"
  }
}

Signature Verification

Webhooks are signed using HMAC-SHA256 (Stripe-style format).

Headers Sent

HeaderDescription
X-Webhook-SignatureSignature: t={timestamp},v1={signature}
X-Webhook-IDUnique event ID
X-Webhook-TimestampUnix timestamp
Content-Typeapplication/json

Verification Example (Node.js)

import crypto from 'crypto';
 
function verifyWebhook(payload, signature, secret) {
  const [timestampPart, signaturePart] = signature.split(',');
  const timestamp = timestampPart.split('=')[1];
  const receivedSig = signaturePart.split('=')[1];
 
  const signedPayload = `${timestamp}.${JSON.stringify(payload)}`;
  const expectedSig = crypto
    .createHmac('sha256', secret)
    .update(signedPayload)
    .digest('hex');
 
  // Use timing-safe comparison
  return crypto.timingSafeEqual(
    Buffer.from(receivedSig),
    Buffer.from(expectedSig)
  );
}
 
// Express middleware example
app.post('/webhooks/workflows', (req, res) => {
  const signature = req.headers['x-webhook-signature'];
 
  if (!verifyWebhook(req.body, signature, process.env.WEBHOOK_SECRET)) {
    return res.status(401).json({ error: 'Invalid signature' });
  }
 
  // Process the webhook
  console.log('Received event:', req.body.type);
  res.status(200).json({ received: true });
});

Delivery Retry Strategy

Failed deliveries are automatically retried with exponential backoff:

AttemptDelay
110 seconds
230 seconds
31 minute
45 minutes
515 minutes
61 hour
76 hours
824 hours

After 8 failed attempts, the delivery is marked as permanently failed.

Circuit Breaker

After 10 consecutive failures, the endpoint is automatically disabled to prevent continued failures. Re-enable by updating the endpoint status:

PUT /api/v1/webhook-endpoints/:id
{
  "status": "active"
}

Test Webhook

Send a test webhook to verify your endpoint is working:

POST /api/v1/webhook-endpoints/:id/test
{
  "eventType": "workflow.completed"
}

Response:

{
  "success": true,
  "data": {
    "delivered": true,
    "httpStatus": 200,
    "responseBody": "{\"received\":true}",
    "eventId": "evt_test_xxx"
  }
}

List Delivery History

GET /api/v1/webhook-endpoints/:id/deliveries?status=failed

Response:

{
  "success": true,
  "data": [
    {
      "id": "del_xxx",
      "eventType": "workflow.completed",
      "eventId": "evt_xxx",
      "status": "failed",
      "httpStatus": 500,
      "attemptCount": 3,
      "lastError": "Connection timeout",
      "nextRetryAt": "2024-01-15T11:00:00.000Z",
      "createdAt": "2024-01-15T10:30:00.000Z"
    }
  ],
  "meta": {
    "cursor": null,
    "hasMore": false
  }
}

Manually Retry Delivery

POST /api/v1/webhook-endpoints/:id/deliveries/:deliveryId/retry

Response:

{
  "success": true,
  "data": {
    "queued": true,
    "deliveryId": "del_xxx"
  }
}

Workflow Trigger Configuration

To trigger workflows from webhooks, configure the trigger in your workflow definition:

Stripe Example

{
  "trigger": {
    "type": "webhook",
    "provider": "stripe",
    "events": ["invoice.paid", "invoice.payment_failed"]
  }
}

HubSpot Example

{
  "trigger": {
    "type": "webhook",
    "provider": "hubspot",
    "events": ["contact.creation", "deal.propertyChange"]
  }
}

Custom Example

{
  "trigger": {
    "type": "webhook",
    "provider": "custom",
    "events": ["order-received"]
  }
}

Testing Webhooks

Using cURL

# Test Stripe webhook
curl -X POST "http://localhost:3000/webhooks/stripe/00000000-0000-0000-0000-000000000001" \
  -H "Content-Type: application/json" \
  -H "stripe-signature: t=1234567890,v1=abc123..." \
  -d '{
    "id": "evt_test",
    "type": "invoice.paid",
    "data": {
      "object": {
        "id": "in_test",
        "amount_paid": 9900
      }
    }
  }'
 
# Test custom webhook
curl -X POST "http://localhost:3000/webhooks/custom/00000000-0000-0000-0000-000000000001/test-event" \
  -H "Content-Type: application/json" \
  -d '{"message": "Hello from test"}'

Stripe CLI

# Forward Stripe webhooks to local endpoint
stripe listen --forward-to localhost:3000/webhooks/stripe/00000000-0000-0000-0000-000000000001
 
# Trigger a test event
stripe trigger invoice.paid

ngrok for Local Development

# Start ngrok tunnel
ngrok http 3000
 
# Use the ngrok URL in provider webhook settings
# https://abc123.ngrok.io/webhooks/stripe/{companyId}

Best Practices

Idempotency

Always design workflows to handle duplicate events:

  • Providers may retry failed deliveries
  • Use eventId to deduplicate
  • Make actions idempotent where possible

Quick Response

Webhook endpoints should respond quickly:

  • Return 200 immediately
  • Process workflows asynchronously
  • Avoid long-running operations in webhook handler

Monitoring

Monitor webhook health:

  • Track signatureVerified status for security issues
  • Monitor status for processing failures
  • Alert on high ignored rates

Security

  • Always verify signatures when available
  • Use HTTPS in production
  • Store webhook secrets securely
  • Rotate secrets periodically

On this page

Webhooks APIProvider Webhook EndpointsPath ParametersStandard ResponseStripe WebhooksHeadersCommon Event TypesExample PayloadStripe ConfigurationHubSpot WebhooksHeadersCommon Event TypesExample PayloadHubSpot ConfigurationQuickBooks WebhooksHeadersCommon Event TypesExample PayloadQuickBooks ConfigurationSalesforce WebhooksContent TypesSOAP XML ResponseCommon Event TypesExample JSON Payload (Change Data Capture)Salesforce ConfigurationPandaDoc WebhooksHeadersCommon Event TypesExample PayloadPandaDoc ConfigurationNetSuite WebhooksHeadersCommon Event TypesExample PayloadNetSuite ConfigurationGmail WebhooksPub/Sub Message FormatGmail ConfigurationCustom WebhooksPath ParametersExampleNon-JSON PayloadsSystem WebhooksNango WebhooksTrigger.dev WebhooksWebhook EventsEvent StructureSignature Verification StatusEvent Processing StatusSignature VerificationHow Verification WorksStripe Signature VerificationHubSpot Signature Verification (v3)QuickBooks Signature VerificationError ResponsesInvalid Signature (401)Invalid JSON (400)Invalid Payload (400)Invalid Pub/Sub Message (400)Rate LimitingOutbound WebhooksWebhook Endpoint ManagementCreate Webhook EndpointRequest BodyExample RequestResponse (201 Created)Webhook Event TypesWebhook Payload FormatFailed Workflow PayloadSignature VerificationHeaders SentVerification Example (Node.js)Delivery Retry StrategyCircuit BreakerTest WebhookList Delivery HistoryManually Retry DeliveryWorkflow Trigger ConfigurationStripe ExampleHubSpot ExampleCustom ExampleTesting WebhooksUsing cURLStripe CLIngrok for Local DevelopmentBest PracticesIdempotencyQuick ResponseMonitoringSecurity