219 lines
5.8 KiB
Markdown
219 lines
5.8 KiB
Markdown
|
|
# WEBHOOK
|
||
|
|
|
||
|
|
Creates a webhook endpoint for event-driven automation. When the webhook URL is called, the script containing the WEBHOOK declaration is executed.
|
||
|
|
|
||
|
|
## Syntax
|
||
|
|
|
||
|
|
```basic
|
||
|
|
WEBHOOK "endpoint-name"
|
||
|
|
```
|
||
|
|
|
||
|
|
## Parameters
|
||
|
|
|
||
|
|
| Parameter | Type | Description |
|
||
|
|
|-----------|------|-------------|
|
||
|
|
| endpoint-name | String | The unique name for the webhook endpoint (alphanumeric, hyphens, underscores) |
|
||
|
|
|
||
|
|
## Description
|
||
|
|
|
||
|
|
The WEBHOOK keyword registers an HTTP endpoint that triggers script execution when called externally. This enables event-driven automation where external systems can notify your bot of events.
|
||
|
|
|
||
|
|
The webhook creates an endpoint at:
|
||
|
|
```
|
||
|
|
POST /api/{botname}/webhook/{endpoint-name}
|
||
|
|
```
|
||
|
|
|
||
|
|
When the webhook is triggered:
|
||
|
|
1. The script containing the WEBHOOK declaration executes
|
||
|
|
2. Request data is available through special variables:
|
||
|
|
- `params` - Query string parameters (e.g., `?id=123`)
|
||
|
|
- `body` - JSON request body
|
||
|
|
- `headers` - HTTP headers
|
||
|
|
- `method` - HTTP method (usually POST)
|
||
|
|
|
||
|
|
## Example
|
||
|
|
|
||
|
|
### Basic Order Webhook
|
||
|
|
|
||
|
|
```basic
|
||
|
|
' order-received.bas
|
||
|
|
WEBHOOK "order-received"
|
||
|
|
|
||
|
|
' Access request data
|
||
|
|
order_id = params.order_id
|
||
|
|
customer_name = body.customer.name
|
||
|
|
customer_email = body.customer.email
|
||
|
|
total = body.total
|
||
|
|
items = body.items
|
||
|
|
|
||
|
|
' Log the order
|
||
|
|
PRINT "Received order: " + order_id
|
||
|
|
|
||
|
|
' Save to database
|
||
|
|
order_data = #{
|
||
|
|
"customer_name": customer_name,
|
||
|
|
"email": customer_email,
|
||
|
|
"total": total,
|
||
|
|
"status": "pending",
|
||
|
|
"created_at": NOW()
|
||
|
|
}
|
||
|
|
SAVE "orders", order_id, order_data
|
||
|
|
|
||
|
|
' Send confirmation email
|
||
|
|
SEND MAIL customer_email, "Order Confirmation", "Thank you for your order #" + order_id
|
||
|
|
|
||
|
|
' Return response (optional - script return value becomes response)
|
||
|
|
result = #{ "status": "ok", "order_id": order_id, "message": "Order received" }
|
||
|
|
```
|
||
|
|
|
||
|
|
### Calling the Webhook
|
||
|
|
|
||
|
|
```bash
|
||
|
|
curl -X POST https://bot.example.com/api/mybot/webhook/order-received \
|
||
|
|
-H "Content-Type: application/json" \
|
||
|
|
-d '{
|
||
|
|
"customer": {
|
||
|
|
"name": "John Doe",
|
||
|
|
"email": "john@example.com"
|
||
|
|
},
|
||
|
|
"items": [
|
||
|
|
{"product": "Widget", "qty": 2, "price": 29.99}
|
||
|
|
],
|
||
|
|
"total": 59.98
|
||
|
|
}'
|
||
|
|
```
|
||
|
|
|
||
|
|
### Payment Notification Webhook
|
||
|
|
|
||
|
|
```basic
|
||
|
|
' payment-webhook.bas
|
||
|
|
WEBHOOK "payment-notification"
|
||
|
|
|
||
|
|
' Verify webhook signature (if provided)
|
||
|
|
signature = headers.x_webhook_signature
|
||
|
|
IF signature = "" THEN
|
||
|
|
PRINT "Warning: No signature provided"
|
||
|
|
END IF
|
||
|
|
|
||
|
|
' Process payment event
|
||
|
|
event_type = body.event
|
||
|
|
payment_id = body.payment.id
|
||
|
|
amount = body.payment.amount
|
||
|
|
status = body.payment.status
|
||
|
|
|
||
|
|
SWITCH event_type
|
||
|
|
CASE "payment.completed"
|
||
|
|
UPDATE "orders", "payment_id=" + payment_id, #{ "status": "paid", "paid_at": NOW() }
|
||
|
|
TALK "Payment " + payment_id + " completed"
|
||
|
|
|
||
|
|
CASE "payment.failed"
|
||
|
|
UPDATE "orders", "payment_id=" + payment_id, #{ "status": "payment_failed" }
|
||
|
|
' Notify customer
|
||
|
|
order = FIND "orders", "payment_id=" + payment_id
|
||
|
|
SEND MAIL order.email, "Payment Failed", "Your payment could not be processed."
|
||
|
|
|
||
|
|
CASE "payment.refunded"
|
||
|
|
UPDATE "orders", "payment_id=" + payment_id, #{ "status": "refunded", "refunded_at": NOW() }
|
||
|
|
|
||
|
|
DEFAULT
|
||
|
|
PRINT "Unknown event type: " + event_type
|
||
|
|
END SWITCH
|
||
|
|
|
||
|
|
result = #{ "received": true }
|
||
|
|
```
|
||
|
|
|
||
|
|
### GitHub Webhook Integration
|
||
|
|
|
||
|
|
```basic
|
||
|
|
' github-webhook.bas
|
||
|
|
WEBHOOK "github-push"
|
||
|
|
|
||
|
|
' GitHub sends event type in header
|
||
|
|
event_type = headers.x_github_event
|
||
|
|
repository = body.repository.full_name
|
||
|
|
pusher = body.pusher.name
|
||
|
|
|
||
|
|
IF event_type = "push" THEN
|
||
|
|
branch = body.ref
|
||
|
|
commits = body.commits
|
||
|
|
commit_count = UBOUND(commits)
|
||
|
|
|
||
|
|
' Log the push
|
||
|
|
message = pusher + " pushed " + commit_count + " commit(s) to " + repository + " (" + branch + ")"
|
||
|
|
PRINT message
|
||
|
|
|
||
|
|
' Notify team via Slack/Teams
|
||
|
|
POST "https://hooks.slack.com/services/xxx", #{ "text": message }
|
||
|
|
|
||
|
|
' Trigger deployment if main branch
|
||
|
|
IF branch = "refs/heads/main" THEN
|
||
|
|
PRINT "Triggering deployment..."
|
||
|
|
POST "https://deploy.example.com/trigger", #{ "repo": repository }
|
||
|
|
END IF
|
||
|
|
END IF
|
||
|
|
|
||
|
|
result = #{ "status": "processed" }
|
||
|
|
```
|
||
|
|
|
||
|
|
## Response Handling
|
||
|
|
|
||
|
|
The webhook automatically returns a JSON response. You can control the response by setting a `result` variable:
|
||
|
|
|
||
|
|
```basic
|
||
|
|
WEBHOOK "my-endpoint"
|
||
|
|
|
||
|
|
' Process request...
|
||
|
|
|
||
|
|
' Simple success response
|
||
|
|
result = #{ "status": "ok" }
|
||
|
|
|
||
|
|
' Or with custom status code
|
||
|
|
result = #{
|
||
|
|
"status": 201,
|
||
|
|
"body": #{ "id": new_id, "created": true },
|
||
|
|
"headers": #{ "X-Custom-Header": "value" }
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
## Security Considerations
|
||
|
|
|
||
|
|
1. **Validate signatures**: Many services (Stripe, GitHub, etc.) sign webhook payloads
|
||
|
|
2. **Verify source**: Check request headers or IP addresses when possible
|
||
|
|
3. **Use HTTPS**: Always use HTTPS endpoints in production
|
||
|
|
4. **Idempotency**: Design webhooks to handle duplicate deliveries gracefully
|
||
|
|
|
||
|
|
```basic
|
||
|
|
WEBHOOK "secure-webhook"
|
||
|
|
|
||
|
|
' Verify HMAC signature
|
||
|
|
expected_signature = HASH(body, secret_key, "sha256")
|
||
|
|
IF headers.x_signature != expected_signature THEN
|
||
|
|
PRINT "Invalid signature - rejecting request"
|
||
|
|
result = #{ "status": 401, "body": #{ "error": "Invalid signature" } }
|
||
|
|
EXIT
|
||
|
|
END IF
|
||
|
|
|
||
|
|
' Continue processing...
|
||
|
|
```
|
||
|
|
|
||
|
|
## Use Cases
|
||
|
|
|
||
|
|
- **E-commerce**: Order notifications, payment confirmations, inventory updates
|
||
|
|
- **CI/CD**: Build notifications, deployment triggers
|
||
|
|
- **CRM**: Lead notifications, deal updates
|
||
|
|
- **IoT**: Sensor data ingestion, device status updates
|
||
|
|
- **Third-party integrations**: Slack commands, form submissions, calendar events
|
||
|
|
|
||
|
|
## Notes
|
||
|
|
|
||
|
|
- Webhook endpoints are registered during script compilation
|
||
|
|
- Multiple scripts can define different webhooks
|
||
|
|
- Webhooks are stored in the `system_automations` table
|
||
|
|
- The endpoint name must be unique per bot
|
||
|
|
- Request timeout is typically 30 seconds - keep processing fast
|
||
|
|
|
||
|
|
## See Also
|
||
|
|
|
||
|
|
- [SET SCHEDULE](./keyword-set-schedule.md) - Time-based automation
|
||
|
|
- [ON](./keyword-on.md) - Database trigger events
|
||
|
|
- [POST](./keyword-post.md) - Making outbound HTTP requests
|