Overview
Webhook Actions let you run custom JavaScript whenever a Vitally Playbook fires. When the playbook triggers, Zudo receives the webhook, runs your script, and automatically updates account traits in Vitally based on the result. This gives you a way to automate logic that would otherwise be manual — calculating dates, transforming values, updating traits conditionally — all without writing a separate integration. Common use cases:- Auto-renewal — calculate and set the next contract expiration date when a subscription renews
- Conditional trait updates — set traits only when certain account conditions are met
- Data transformations — derive new values from existing traits (e.g., days until renewal)
- Audit trails — stamp a date trait with today’s date when a specific event occurs
Setup
Create the action in Zudo
- Go to Settings → Connections.
- Find your Vitally connection and open it.
- Under Playbook Webhook Actions, click + Add Action.
- Fill in the fields:
- Name — a descriptive label (e.g., “Auto-Renewal Handler”)
- Description — what this action does (optional but recommended)
- JavaScript Code — your custom logic (see Writing Your Script below)
- Enabled — toggle to activate or deactivate the action
- Save the action.
Copy the webhook URL
After saving, each action gets a unique webhook URL. Click the copy button next to the action to copy it.The URL format is:Keep this URL — you’ll paste it into Vitally in the next step.
Configure the Vitally Playbook
- In Vitally, create or open the Playbook you want to use.
- Add a Webhook action step.
- Paste your webhook URL into the URL field.
- Set your trigger conditions as needed.
- Activate the Playbook.
Writing Your Script
Your script runs in a secure JavaScript sandbox. Two objects are available as inputs, and your script must return either a trait update object ornull.
Available Variables
account
The full Vitally account object, fetched fresh from the Vitally API at the time the webhook fires. Use this to get the latest trait values.
payload
The raw webhook payload sent by Vitally. It contains the playbook context and a snapshot of the account at the time of the trigger.
account object nests traits under account.traits.key, but the payload object puts traits directly on payload.match.account.key. When accessing the same trait from either source:
account when you need the most up-to-date value. Use payload when you need the value as it was at the moment the Playbook triggered.
Helper Functions
The sandbox includes built-in helpers for common date and interval operations.Date Helpers
| Function | Description | Example |
|---|---|---|
addMonths(date, months) | Add months to a date | addMonths("2026-01-31", 1) → "2026-02-28" |
addDays(date, days) | Add days to a date | addDays("2026-01-15", 30) → "2026-02-14" |
addYears(date, years) | Add years to a date | addYears("2026-01-15", 1) → "2027-01-15" |
today() | Get today’s date as a string | today() → "2026-02-04" |
now() | Get current datetime (ISO) | now() → "2026-02-04T12:30:45.123Z" |
parseDate(date) | Parse any date to ISO format | parseDate("Jan 15, 2026") → "2026-01-15" |
isPast(date) | Check if a date is in the past | isPast("2025-01-01") → true |
isFuture(date) | Check if a date is in the future | isFuture("2027-01-01") → true |
diffDays(date1, date2) | Days between two dates | diffDays("2026-01-01", "2026-01-15") → 14 |
Interval Helpers
| Function | Description | Example |
|---|---|---|
intervalToMonths(interval) | Convert interval name to months | intervalToMonths("annual") → 12 |
addInterval(date, interval) | Add a named interval to a date | addInterval("2026-01-15", "annual") → "2027-01-15" |
addInterval and intervalToMonths:
| Interval name | Months |
|---|---|
monthly, month to month | 1 |
quarterly | 3 |
semi-annual, semiannual, bi-annual | 6 |
annual, yearly | 12 |
biennial, 2-year | 24 |
triennial, 3-year | 36 |
Returning a Result
Your script must return one of two things:Update traits
Return an object with atraits key to update the account in Vitally:
current_contract_expiration). Values can be strings, numbers, booleans, or ISO date strings.
Skip the update
Returnnull when you don’t want to make any changes:
null when conditions aren’t met, required data is missing, or you want to skip a particular trigger.
Code Examples
Auto-Renewal: Calculate Next Expiration Date
Use this to automatically advance the contract expiration date when a subscription renews.Set a Date Trait to Today
Use this to stamp a date trait whenever a Playbook fires — useful for audit trails.Conditional Update
Only update a trait when a specific condition is true; skip otherwise.Calculate Days Until Renewal
Compute a numeric trait from an existing date trait.Troubleshooting
| Error | Cause | Solution |
|---|---|---|
Webhook action not found | Invalid token in the URL | Check that the webhook URL is correct and hasn’t changed |
Action is disabled | The action’s toggle is off | Enable the action in Settings → Connections |
Connection is disabled | The Vitally connection is off | Enable the Vitally connection in Connections |
Missing account in payload | Malformed Vitally webhook | Verify the Playbook webhook step is configured correctly |
Execution timed out | Your script took more than 5 seconds | Simplify the logic; remove any loops or heavy operations |
Invalid date: ... | A bad value was passed to a date helper | Check that the date string is valid before passing it |
- Use
console.login your script to trace values. Logs appear in the execution stats view for the action. - Check the action card in Settings for trigger count, error count, and the last error message.
- Start with a minimal script that just returns
{ traits: { test_trait: today() } }to confirm the webhook is firing correctly before adding complex logic. - The code editor validates syntax before saving, but runtime errors only surface after a real trigger.
Best Practices
- Validate inputs before using them. Check for
nullorundefinedbefore accessing nested properties or passing values to helpers. - Return
nullinstead of throwing. If required data is missing or conditions aren’t met, returnnullrather than letting the script error out. - Use
accountfor the latest values. Thepayloadsnapshot may be slightly stale; preferaccount.traitswhen you need the current state. - Keep scripts focused. One action per Playbook trigger is easier to debug than one script that tries to do everything.
- Avoid logging sensitive data. Don’t log full account objects or traits that contain PII.
- Test in staging first. Verify your Playbook and webhook work end-to-end in a non-production environment before activating in production.
