Skip to content

Triggers

Triggers allow you to automatically execute actions when specific events occur in a collection. Triggers can make HTTP requests to external services when records are created, updated, or deleted.

Triggers are configured at the collection level and automatically fire when:

  • A new record is created
  • An existing record is updated
  • A record is deleted

Triggers send HTTP requests (webhooks) to configured URLs with event details.

Triggers live in the collection’s options block in the IaC YAML (see IaC); vski apply converges them like any other collection setting.

vski/default/collections.yaml
version: 1
database: default
collections:
notifications:
type: base
fields:
- {name: title, type: text, required: true}
- {name: message, type: text}
- {name: status, type: select, options: {values: [active, inactive]}}
options:
triggers:
- action: create
url: https://api.example.com/webhook
method: POST
headers:
Content-Type: application/json
X-Webhook-Source: vski
- action: update
url: https://api.example.com/webhook
method: PATCH
- action: delete
url: https://api.example.com/webhook
method: DELETE

Fires when a new record is created:

- action: create
url: https://api.example.com/new-record
method: POST
headers:
Content-Type: application/json
Authorization: "Bearer your-token"

Fires when a record is updated:

- action: update
url: https://api.example.com/updated-record
method: PATCH

Fires when a record is deleted:

- action: delete
url: https://api.example.com/deleted-record
method: DELETE

When a trigger fires, it sends a JSON payload to the configured URL:

{
"database": "default",
"collection": "notifications",
"id": "record-id-123",
"action": "create",
"timestamp": "2024-01-01T00:00:00Z",
"record": {
"id": "record-id-123",
"title": "New Notification",
"message": "Hello World",
"status": "active",
"created": "2024-01-01T00:00:00Z",
"updated": "2024-01-01T00:00:00Z"
}
}
collections:
alerts:
type: base
fields:
- {name: severity, type: select, options: {values: [low, medium, high, critical]}}
- {name: message, type: text, required: true}
- {name: source, type: text}
options:
triggers:
- action: create
url: https://hooks.slack.com/services/YOUR/SLACK/WEBHOOK
method: POST
headers:
Content-Type: application/json

The Slack webhook would receive:

{
"text": "New Alert: Critical - Server down"
}
collections:
contact_submissions:
type: base
fields:
- {name: name, type: text, required: true}
- {name: email, type: email, required: true}
- {name: message, type: text, required: true}
options:
triggers:
- action: create
url: https://email-service.com/send
method: POST
headers:
Authorization: "Bearer ${EMAIL_API_KEY}"
Content-Type: application/json
collections:
users:
type: auth
fields:
- {name: email, type: email, required: true}
- {name: password, type: text, required: true}
- {name: name, type: text}
options:
triggers:
- action: create
url: https://audit-log.example.com/entry
method: POST
headers:
Authorization: "Bearer ${AUDIT_TOKEN}"
- action: update
url: https://audit-log.example.com/entry
method: POST
headers:
Authorization: "Bearer ${AUDIT_TOKEN}"
- action: delete
url: https://audit-log.example.com/entry
method: POST
headers:
Authorization: "Bearer ${AUDIT_TOKEN}"

Secrets like EMAIL_API_KEY should not be committed in plain YAML headers. Prefer pipeline secrets or an environment-driven templating step in CI when the values are sensitive.

collections:
leads: # CRM integration
type: base
fields:
- {name: name, type: text, required: true}
- {name: email, type: email, required: true}
- {name: company, type: text}
options:
triggers:
- action: create
url: https://crm.example.com/api/leads
method: POST
headers:
Authorization: "Bearer ${CRM_API_KEY}"
Content-Type: application/json
events: # analytics integration
type: base
fields:
- {name: eventName, type: text, required: true}
- {name: userId, type: text}
- {name: properties, type: json}
options:
triggers:
- action: create
url: https://analytics.example.com/track
method: POST
headers:
Authorization: "Bearer ${ANALYTICS_KEY}"
collections:
orders:
type: base
fields:
- {name: customerId, type: text, required: true}
- {name: items, type: json, required: true}
- {name: total, type: number, required: true}
- {name: status, type: select, options: {values: [pending, processing, shipped, delivered]}}
options:
triggers:
- action: create
url: http://127.0.0.1:3000/api/workflows/runs
method: POST
headers:
Authorization: "Bearer ${WORKFLOW_API_KEY}"
Content-Type: application/json

Pipelines can subscribe to collection events directly in their own definition, so no collection trigger or auth header wiring is needed:

{
"name": "orders-pipeline",
"triggers": [
{ "type": "collection", "database": "default", "collection": "orders", "actions": ["create"] }
]
}

The record (and its id, action, database and collection) reaches every step as {{trigger.record.*}} templates.

collections:
products:
type: base
fields:
- {name: sku, type: text, required: true}
- {name: name, type: text, required: true}
- {name: price, type: number, required: true}
- {name: stock, type: number}
options:
triggers:
- action: create
url: https://external-db.example.com/sync
method: POST
headers:
Authorization: "Bearer ${SYNC_KEY}"
- action: update
url: https://external-db.example.com/sync
method: PATCH
headers:
Authorization: "Bearer ${SYNC_KEY}"
- action: delete
url: https://external-db.example.com/sync
method: DELETE
headers:
Authorization: "Bearer ${SYNC_KEY}"
# action is one of: create | update | delete
# method is one of: GET | POST | PUT | PATCH | DELETE (optional, default POST)
- action: create
url: https://api.example.com/hook
method: POST
headers: {Content-Type: application/json} # optional custom headers
body: "custom body" # optional custom body

You can define multiple triggers for different events, and multiple triggers for the same event; each fires independently:

options:
triggers:
- {action: create, url: "https://api.example.com/new", method: POST}
- {action: create, url: "https://slack.example.com/webhook", method: POST}
- {action: create, url: "https://email.example.com/send", method: POST}
- {action: update, url: "https://api.example.com/updated", method: PATCH}
- {action: delete, url: "https://api.example.com/deleted", method: DELETE}

Edit the YAML and apply. The options block is authoritative: whatever the file says is what the collection carries after vski apply.

View webhook execution logs from the command line or the SDK:

Terminal window
vski webhooks logs --collection notifications
vski webhooks logs --status failed
logs = await client.webhook_logs.list(
collection="notifications", status="success", page=1, per_page=50
)
for log in logs:
print(log["url"], log["status"], log["tries"], log.get("error_msg"))
Event occurs in collection
↓
Trigger is matched
↓
HTTP request is queued
↓
Request is sent to target URL
↓
Response is received
↓
Log entry is created
↓
(If failed) Retry mechanism kicks in

When a trigger fails (non-2xx response):

  1. Error is logged
  2. Retry counter is incremented
  3. Trigger will retry on next attempt
Terminal window
vski webhooks logs --status failed
for log in await client.webhook_logs.list(status="failed"):
print("failed:", log["url"], log.get("error_msg"), "tries:", log["tries"])
  1. Use HTTPS: always use HTTPS URLs for triggers
  2. Secure tokens: store API tokens securely (environment variables)
  3. Verify signatures: implement webhook signature verification on the receiving end
  4. Rate limiting: implement rate limiting on webhook endpoints
  5. Validate payloads: always validate incoming webhook data
  6. Use authentication: include authentication headers in triggers
  7. Monitor logs: regularly check webhook logs for failures
  8. Test endpoints: verify webhook endpoints are working before production
  9. Handle duplicates: make webhook handlers idempotent
  10. Timeout handling: set appropriate timeouts for webhook requests
FeatureTriggersCron Jobs
WhenOn record eventsOn schedule
Use CasesReal-time notifications, data syncPeriodic tasks, reports
LatencyNear real-timeBased on schedule
ReliabilityDepends on endpointBuilt-in retry
APICollection optionsclient.cron

Use triggers for:

  • Immediate notifications
  • Real-time data synchronization
  • Event-driven workflows
  • Third-party integrations

Use cron jobs for:

  • Scheduled tasks
  • Periodic reports
  • Maintenance operations
  • Batch processing

Create a test record to trigger the webhook, then check the logs:

await client.collection("notifications").create(
{"title": "Test Notification", "message": "This is a test", "status": "active"}
)
logs = await client.webhook_logs.list(collection="notifications", page=1, per_page=10)
print(logs)

Use webhook testing services like:

collections:
test_collection:
type: base
fields:
- {name: data, type: text, required: true}
options:
triggers:
- action: create
url: https://webhook.site/your-unique-id
method: POST
await client.collection("test_collection").create({"data": "Test data"})
# then check webhook.site to see the payload
MethodEndpointDescription
POST/api/collectionsCreate collection with triggers
PATCH/api/collections/:nameUpdate collection triggers
GET/api/webhooks/logsList webhook execution logs
  1. Keep endpoints fast: webhook endpoints should respond quickly
  2. Return success codes: always return 2xx status codes on success
  3. Handle failures gracefully: don’t throw unhandled errors
  4. Log everything: keep detailed logs for debugging
  5. Monitor regularly: check webhook logs for issues
  6. Use idempotency: handle duplicate webhook deliveries
  7. Implement timeouts: don’t let webhook handlers hang
  8. Validate input: always validate webhook payloads
  9. Test thoroughly: test triggers before production
  10. Document triggers: the YAML is the documentation