Guide: Webhook Automation
Build a workflow that receives a GitHub push event, extracts commit information, and sends an email notification to your team.
What you will build:
Webhook Trigger (GitHub) → Transform → Send Email (Resend)
Time: ~15 minutes
Prerequisites:
- Tensorify account with a workspace
- Resend account with a verified domain (resend.com)
- A GitHub repository where you can add webhooks
- CLI installed (only for local testing):
curl -fsSL https://cli.tensorify.io/install | sh— or skip and deploy with Managed mode
- Go to app.tensorify.io → dashboard
- Click New Workflow and name it
github-push-notifications - Click Create
Drag a Webhook node onto the canvas. In its settings panel:
- Path:
/github/push - Method:
POST - Verification Type:
hmac-signature - Signature Header Name:
X-Hub-Signature-256 - Signature Algorithm:
sha256 - Response Mode:
ack-immediately
Leave the Secret field empty for now — you will fill it after GitHub generates the webhook.
Drag a Transform node onto the canvas and connect the Webhook's payload output to Transform's input.
In the Transform settings, set the template to:
{
"pusherName": "{{ webhook.body.pusher.name }}",
"repoName": "{{ webhook.body.repository.full_name }}",
"branch": "{{ webhook.body.ref }}",
"commitCount": "{{ webhook.body.commits.length }}",
"latestMessage": "{{ webhook.body.head_commit.message }}",
"compareUrl": "{{ webhook.body.compare }}"
}
First, add your RESEND_API_KEY to your workspace:
- Go to Settings → Env Vars (or directly to the Env Vars page)
- Add a variable named
RESEND_API_KEYwith your Resend API key
Now drag a Send Email node onto the canvas and connect Transform's result to Send Email's text input.
The text input expects a plain text string, but Transform produces an object. To build the email body as a string, insert a Code node between Transform and Send Email instead. See Step 4b below.
Drag a Code node between Transform and Send Email. Connect Transform's result to Code's input.
repo = input.get("repoName", "unknown")
branch = input.get("branch", "").replace("refs/heads/", "")
pusher = input.get("pusherName", "someone")
count = input.get("commitCount", 0)
message = input.get("latestMessage", "")
url = input.get("compareUrl", "")
return f"""{pusher} pushed {count} commit(s) to {repo} ({branch})
Latest: {message}
Compare: {url}"""
Connect Code's result to Send Email's text input.
In the Send Email settings:
- From:
[email protected] - From Name:
GitHub Notifier - To:
[email protected] - Subject:
New push to {{ transform.repoName }} ({{ transform.branch }})
Using Managed mode? Skip this step — deploy directly from Step 6 with Managed execution and trigger via the webhook URL.
Install the CLI, authenticate, and start a runner:
tensorify init
tensorify login # opens browser — or use --api-key on init
tensorify runner start
In the canvas editor, open the Webhook node settings and add a mock payload:
{
"pusher": { "name": "octocat" },
"repository": { "full_name": "octocat/my-repo" },
"ref": "refs/heads/main",
"commits": [{}],
"head_commit": { "message": "Fix typo in README" },
"compare": "https://github.com/octocat/my-repo/compare/abc...def"
}
Click Test → select the Send Email node → click Run to Selected. Check your inbox.
- Click Deploy in the toolbar
- Set Execution Mode to CLI (self-hosted runner) or Managed for cloud execution
- Copy the Webhook URL
In GitHub:
- Go to your repository → Settings → Webhooks → Add webhook
- Set Payload URL to your Tensorify webhook URL
- Set Content type to
application/json - Set Secret to a random string (e.g., from
openssl rand -hex 32) - Copy that secret back into the Webhook node's Secret field in Tensorify
If using self-hosted execution, ensure your runner is connected (tensorify runner start or tensorify runner install on a server), then deploy from the Deploy dialog with execution mode CLI or Auto.
Make a commit and push to your repository. Within seconds you should receive an email and see a completed job on the Jobs page.
- How to configure HMAC-signature verification for GitHub webhooks
- How to use Transform to extract specific fields from a complex payload
- How to use a Code node to build a formatted string
- How to deploy and verify a production webhook workflow
- Build a REST API endpoint — return a synchronous response
- Webhook Trigger reference — full settings documentation
- Send Email reference — HTML emails, dynamic recipients
The pattern in this guide — receive event, transform data, take action — applies to any webhook source:
- Stripe — Send a receipt email on
checkout.session.completed, update your database oninvoice.paid, alert oncharge.failed - GitHub — Notify a Slack channel on pull request, deploy on tag push, run CI status checks
- Shopify — Sync new orders to a CRM, trigger fulfillment workflows on
orders/create - Twilio — Process incoming SMS, forward voicemail transcriptions, build conversational flows
- Custom apps — Any service that sends HTTP POST events can trigger a Tensorify workflow
The same workflow structure works for Stripe. Replace the GitHub webhook with a Stripe webhook endpoint, set the signing secret for HMAC verification, and extract fields like event.data.object.customer_email in the Transform node. Tensorify's HMAC verification supports Stripe's v1 signature scheme out of the box.
Complete workflow matching this guide: raw GitHub webhook HMAC → transform → code → Resend email.
import Webhook from @tensorify/webhook-trigger:4.0.0
import Transform from @tensorify/transform:3.0.0
import Code from @tensorify/code:3.0.0
import Resend from @tensorify/resend-send-email:1.0.0
node webhook @tensorify/webhook-trigger:4.0.0 {
provider = "github"
path = "/github/push"
method = "POST"
mockPayload = {
"pusher": { "name": "octocat" },
"repository": { "full_name": "octocat/my-repo" },
"ref": "refs/heads/main",
"commits": [{}],
"head_commit": { "message": "Fix typo in README" },
"compare": "https://github.com/octocat/my-repo/compare/abc...def"
}
_secret_bindings = { WEBHOOK_SECRET: "GITHUB_WEBHOOK_SECRET" }
}
node fields @tensorify/transform:3.0.0 {
# Shape depends on Transform UI schema; agents should confirm via get_plugin_manifest.
# Equivalent intent: extract pusher name, repo, branch, commit message, compare URL.
}
node format @tensorify/code:3.0.0 {
code = """
b = input if isinstance(input, dict) else {}
# When wired from webhook.payload, body is under body / or top-level depending on envelope.
data = b.get("body", b)
pusher = (data.get("pusher") or {}).get("name", "someone")
repo = (data.get("repository") or {}).get("full_name", "unknown/repo")
ref = data.get("ref", "")
branch = ref.replace("refs/heads/", "") if isinstance(ref, str) else ""
msg = (data.get("head_commit") or {}).get("message", "(no message)")
compare = data.get("compare", "")
text = f"Push by {pusher} to {repo} ({branch})\n\n{msg}\n\n{compare}"
return {"text": text, "subject": f"[{repo}] push to {branch}"}
"""
}
node email @tensorify/resend-send-email:1.0.0 {
from = "[email protected]"
to = "[email protected]"
subject = "{{ format.subject }}"
text = "{{ format.text }}"
_secret_bindings = { RESEND_API_KEY: "RESEND_API_KEY" }
}
webhook.payload -> format.input
format.result -> email.input
Policy note: provider = "github" here is raw webhook HMAC only. For GitHub App installation events use @tensorify/github-trigger:1.0.0, not this path.