Webhook bridge for chaining Clay tables.
Turn Clay webhook tables into functions.
What it does
Mold sits between two Clay tables and bridges them synchronously:
Table A (HTTP API column)
→ POST to Mold
→ Mold forwards payload to Table B's webhook
→ Table B runs all its columns
→ Table B's HTTP API column POSTs result back to Mold
→ Mold returns the result as the response to Table A
Table A sends data, Table B enriches it, Table A gets the enriched result back in a single request/response cycle. No polling, no queues, no database.
Getting started
Step 1: Generate your API token
Open your terminal and run:
openssl rand -hex 32
Copy the output. This is your API_TOKEN — you'll use it in the next step and when configuring Clay.
Step 2: Deploy
Pick a platform and click to deploy:
Render step-by-step
- Click the Deploy to Render button above
- Sign in to Render (or create a free account)
- You'll see a "Blueprint Name" field — you can name it anything (e.g.
mold), it doesn't matter - Below that you'll see environment variables. Find
API_TOKENand paste the token you generated in Step 1 - Leave all other fields as they are — the defaults are fine. Render reads the
render.yamlfile automatically, so you don't need to configure anything else - Click Apply
- Wait for the deploy to finish (usually under a minute). You'll see the status turn green
- Click on your service name. Your server URL is shown near the top of the page, something like
https://mold-xxxx.onrender.com - Copy that URL — you'll need it for Clay
Note: Render's free tier spins down after inactivity. The first request after a cold start takes ~30 seconds. If this is a problem, upgrade to their paid tier or use Railway.
Railway step-by-step
- Click the Deploy on Railway button above
- Sign in to Railway (or create an account)
- If asked to name the project, pick anything you like (e.g.
mold) - Railway will show you the environment variables. Find
API_TOKENand paste the token you generated in Step 1 - Leave everything else as-is — Railway detects the project setup automatically from the repo
- Click Deploy
- Wait for the build to finish (usually under two minutes)
- Once deployed, go to your service → Settings → Networking → Generate Domain
- Railway will assign a public URL like
https://mold-production-xxxx.up.railway.app - Copy that URL — you'll need it for Clay
Note: If Railway asks about a config file or Dockerfile, you don't need to select anything — it auto-detects from the repo.
Docker / VPS / other
git clone https://github.com/eliasstravik/mold.git
cd mold
npm install
npm run build
API_TOKEN=your-token-from-step-1 npm start
Or with Docker:
docker build -t mold .
docker run -p 3000:3000 -e API_TOKEN=your-token-from-step-1 mold
Your server URL is whatever domain or IP points to your server (e.g. https://mold.fly.dev).
Step 3: Get your Table B webhook URL
- In Clay, open (or create) the table that will do the enrichment work — this is Table B
- Table B needs to be set up to receive data via webhook. Click Add Source (or check your existing source) and select Webhook
- Clay will show you a webhook URL. It looks something like:
https://api.clay.com/v3/sources/webhook/abc123def456 - Copy that URL — you'll paste it into Table A in the next step
Step 4: Configure Table A (the table that sends data)
This is where you send data out to be enriched. You'll add an HTTP API column that sends each row's data to Mold.
- Open your Table A in Clay
- Click Add Column and select HTTP API
- Configure the HTTP API column with these exact settings:
Method: POST
URL: Paste your Mold server URL from Step 2 and add /bridge at the end. For example, if your Mold URL is https://mold-a1b2.onrender.com, the full URL would be:
https://mold-a1b2.onrender.com/bridge
Headers: Click "Add Header" and add exactly one header:
- Key:
Authorization - Value:
Bearer YOUR_API_TOKEN
Replace YOUR_API_TOKEN with the actual token you generated in Step 1. Make sure to include the word Bearer followed by a space before the token. For example, if your token is e4f9a1..., the full value would be Bearer e4f9a1....
Body: This is the data you want to send to Table B for enrichment. It's a JSON object with these parts:
_mold_target_url(required) — tells Mold where to forward the data. Use the Table B webhook URL you copied in Step 3_mold_target_auth_token(optional) — if Table B's webhook has an authentication token enabled, include it here. Mold will send it in thex-clay-webhook-authheader when forwarding to Table B- Your actual data — the fields you want Table B to work with. Use the
{}button or type/in Clay to insert column references from Table A
For example, say Table A has columns called "First Name", "Email", and "Company". Your body would look like:
{
"_mold_target_url": "https://api.clay.com/v3/sources/webhook/abc123def456",
"first_name": "{{First Name}}",
"email": "{{Email}}",
"company": "{{Company}}"
}
Or if Table B's webhook requires auth:
{
"_mold_target_url": "https://api.clay.com/v3/sources/webhook/abc123def456",
"_mold_target_auth_token": "table-b-auth-token-here",
"domain": "{{Company Domain}}"
}
A few things to note:
- Replace the
_mold_target_urlvalue with the actual Table B webhook URL from Step 3 _mold_target_auth_tokenis optional — only include it if Table B's webhook requires it- To insert column references, either click the
{}button or type/in any field in Clay's HTTP API column editor — they look like{{Column Name}} - You can include as many or as few data fields as you want — whatever Table B needs to do its job
- Mold strips both
_mold_target_urland_mold_target_auth_tokenbefore forwarding, so Table B only receives the actual data fields
Step 5: Send a test row from Table A
Before you can configure Table B's response column, you need to fire at least one request so that Table B receives the data and creates the columns you'll reference.
- Go back to Table A
- Make sure there's at least one row with data in it
- Run that row (or the whole table) so the HTTP API column fires
- The request will hit Mold, which forwards it to Table B — this first request will time out, and that's expected. The point is just to get the data into Table B so the columns show up
Now go to Table B. You should see a new row with the data you sent from Table A, plus a _mold_callback_url column that Mold injected. You'll need this column in the next step.
Step 6: Configure Table B (the table that enriches and responds)
Table B now has the forwarded data from Mold. You can add whatever enrichment columns you want (lookups, AI, etc.). Once those are set up, you need to add an HTTP API column as the very last column to send the results back.
- Open Table B in Clay
- Add whatever enrichment/processing columns you need — these will run before the final HTTP API column
- Click Add Column and select HTTP API — add it as the last column in the table
- Configure it with these exact settings:
Method: POST
URL: Click the {} button (or type /) and select the _mold_callback_url column. This column was automatically created when Mold forwarded the data from Table A — it contains a unique URL like https://mold-a1b2.onrender.com/callback/d4e5f6-... that routes the result back to the correct request. Your URL field should show:
{{_mold_callback_url}}
Headers: Click "Add Header" and add exactly one header:
- Key:
Authorization - Value:
Bearer YOUR_API_TOKEN(the same token from Step 1 — same as Table A)
Body: This is the enriched data you want sent back to Table A. Use the {} button or type / to reference Table B's columns — including any enrichment columns you added. For example:
{
"name": "{{Name}}",
"email": "{{Email}}",
"company": "{{Company}}",
"enriched_title": "{{Job Title}}",
"enriched_industry": "{{Industry}}"
}
Whatever you put in this body is exactly what Table A will receive back as the response to its HTTP API column.
Step 7: Test the full flow
Now that both tables are configured, run a row in Table A again. This time the full round-trip should work:
- Table A sends the data to Mold (
https://mold-a1b2.onrender.com/bridge) - Mold forwards it to Table B
- Table B runs all its columns (including your enrichment columns)
- Table B's final HTTP API column sends the result back to Mold (using
_mold_callback_url) - Mold returns the result to Table A
Table A's HTTP API column will now show the enriched response. You can reference these fields in subsequent columns in Table A.
That's it
You're done. From now on, every row that runs in Table A will automatically go through Table B and come back enriched.
Environment variables
Only API_TOKEN is required. The rest are optional tuning knobs:
| Variable | Required | Default | Description |
|---|---|---|---|
API_TOKEN |
Yes | Bearer token for authenticating requests | |
TIMEOUT_MS |
No | 300000 |
Max ms to wait for callback (default 5 min) |
MAX_PENDING |
No | 100 |
Max concurrent pending requests |
PORT |
No | 3000 |
Server port |
How it works
Mold is a single-file Hono server (~170 lines) with two endpoints:
POST https://your-mold-server.com/bridge(authenticated) receives Table A's request, generates a unique callback URL, forwards the payload to Table B, and holds the connection open until Table B responds.POST https://your-mold-server.com/callback/:id(authenticated, same bearer token) receives Table B's completed result and routes it back to the waiting Table A connection. You never call this directly — Mold generates the full URL (e.g.https://mold-a1b2.onrender.com/callback/d4e5f6...) and injects it into the payload as_mold_callback_url.
Request parking uses an in-memory Map of Promise resolvers. This means Mold runs as a single process. If you need horizontal scaling, you would need shared state (Redis, etc.), but for most Clay workflows a single instance is more than sufficient.
Development
npm install
npm run dev
| Command | Description |
|---|---|
npm run dev |
Start dev server with hot reload |
npm run build |
Type-check and production build |
npm start |
Run production build |
Architecture
Built with Hono and TypeScript. Two production dependencies. Deploys anywhere that runs Node.js.
src/
index.ts The entire server
Security measures
- Bearer token auth on all endpoints
- 1MB body size limit on all endpoints
- Request capacity cap (default 100 concurrent)
- Forward timeout (30s) with AbortSignal
- Timing-safe token comparison (Hono built-in)
- Callback UUIDs are cryptographically random and never logged
Contributing
See CONTRIBUTING.md for development setup and guidelines.
Security
See SECURITY.md for the security model and how to report vulnerabilities.
License
{
"request": {
"method": "GET",
"url": "https://api.eliasstravik.com/projects/mold"
},
"response": {
"status": "200 OK",
"headers": {
"content-type": "application/json; charset=utf-8",
"x-powered-by": "one person + systems"
},
"body": {
"name": "mold",
"description": "Webhook bridge for chaining Clay tables",
"language": "TypeScript",
"stars": 7,
"forks": 1,
"repo": "https://github.com/eliasstravik/mold",
"readme": "https://github.com/eliasstravik/mold#readme"
}
}
}