Webhooks
Real-time notifications for launch events.
Overview
Webhooks let you receive HTTP callbacks when events occur:
- Launch status changes
- Vesting claimed
- DAO proposal updates
- Fee claims
Setting Up Webhooks
Per-Launch Webhook
Include webhook config in your launch request:
{
"name": "MyToken",
"ticker": "MTK",
"totalSupply": 1000000000,
"webhookUrl": "https://yourapp.com/webhook/etch",
"webhookSecret": "your-hmac-secret"
}Global Webhooks (Coming Soon)
Configure webhooks for all launches in your dashboard.
Webhook Events
Launch Events
| Event | Description |
|---|---|
launch.queued | Launch added to queue |
launch.processing | Launch execution started |
launch.step_complete | Individual step completed |
launch.complete | All steps finished |
launch.failed | Launch failed |
Vesting Events
| Event | Description |
|---|---|
vesting.created | Vesting contract created |
vesting.cliff_reached | Cliff period ended |
vesting.claimed | Tokens claimed by beneficiary |
vesting.complete | All tokens vested |
DAO Events
| Event | Description |
|---|---|
dao.created | DAO realm created |
dao.proposal_created | New proposal submitted |
dao.vote_cast | Vote recorded |
dao.proposal_passed | Proposal approved |
dao.proposal_failed | Proposal rejected |
Webhook Payload
All webhooks follow this structure:
{
"event": "launch.complete",
"timestamp": "2026-02-15T12:00:00Z",
"launchId": "launch_abc123",
"data": {
// Event-specific data
}
}Example: Launch Complete
{
"event": "launch.complete",
"timestamp": "2026-02-15T12:00:30Z",
"launchId": "launch_abc123",
"data": {
"mint": "ABC123...",
"metadata": "META456...",
"vestingContract": "VEST789...",
"dao": {
"realm": "REALM123...",
"treasury": "TRES456..."
},
"pool": {
"address": "POOL789...",
"lpMint": "LP012..."
},
"transactions": [
{ "type": "mint", "signature": "5abc..." },
{ "type": "vesting", "signature": "5def..." }
]
}
}Example: Launch Failed
{
"event": "launch.failed",
"timestamp": "2026-02-15T12:00:30Z",
"launchId": "launch_abc123",
"data": {
"error": {
"code": "insufficient_balance",
"message": "Wallet has insufficient SOL for transaction",
"step": "create_pool"
},
"partialResults": {
"mint": "ABC123..."
}
}
}HMAC Verification
All webhooks are signed with HMAC-SHA256. Always verify the signature.
Headers
X-Etch-Signature: sha256=abc123...
X-Etch-Timestamp: 1700000000
X-Etch-Event: launch.completeVerification Code
import crypto from "crypto";
function verifyWebhook(
payload: string,
signature: string,
timestamp: string,
secret: string
): boolean {
// Check timestamp to prevent replay attacks (5 min window)
const now = Math.floor(Date.now() / 1000);
const ts = parseInt(timestamp);
if (Math.abs(now - ts) > 300) {
return false;
}
// Compute expected signature
const signedPayload = `${timestamp}.${payload}`;
const expectedSig = crypto
.createHmac("sha256", secret)
.update(signedPayload)
.digest("hex");
// Constant-time comparison
const expected = `sha256=${expectedSig}`;
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expected)
);
}
// Express middleware
app.post("/webhook/etch", express.raw({ type: "application/json" }), (req, res) => {
const signature = req.headers["x-etch-signature"];
const timestamp = req.headers["x-etch-timestamp"];
if (!verifyWebhook(req.body.toString(), signature, timestamp, SECRET)) {
return res.status(401).send("Invalid signature");
}
const event = JSON.parse(req.body);
console.log("Received:", event.event);
res.status(200).send("OK");
});Retry Policy
If your endpoint returns a non-2xx status, Etch retries:
| Attempt | Delay |
|---|---|
| 1 | Immediate |
| 2 | 30 seconds |
| 3 | 2 minutes |
| 4 | 10 minutes |
| 5 | 1 hour |
After 5 failures, the webhook is marked as failed in your dashboard.
Circuit Breakers
Etch uses per-endpoint circuit breakers to prevent cascade failures. If your endpoint fails 3 times consecutively, the circuit “opens” and further deliveries are paused for 5 minutes.
Circuit Breaker States
| State | Description |
|---|---|
closed | Normal operation, webhooks delivered |
open | Deliveries paused (3+ failures) |
half-open | Testing if endpoint recovered |
After 5 minutes, the circuit enters half-open state and attempts one delivery. If it succeeds, the circuit closes. If it fails, it re-opens.
Check circuit breaker status via the Health endpoint:
curl https://etch.film.fun/api/health | jq '.circuitBreakers'Avoiding Circuit Opens
- Respond quickly — Return 200 within 10 seconds
- Use async processing — Acknowledge immediately, process later
- Monitor your endpoint — Set up uptime monitoring
- Handle errors gracefully — Return 500 (retryable) not 400 (permanent)
Best Practices
- Always verify signatures — Never trust unverified webhooks
- Respond quickly — Return 200 within 5 seconds, process async
- Handle duplicates — Use
launchIdas idempotency key - Log everything — Keep webhook logs for debugging
- Use HTTPS — Webhook URLs must be HTTPS in production
Testing Webhooks
Use the test endpoint to simulate events:
curl -X POST https://etch.film.fun/api/webhooks/test \
-H "Authorization: Bearer TOKEN" \
-H "Content-Type: application/json" \
-d '{
"webhookUrl": "https://yourapp.com/webhook/etch",
"event": "launch.complete"
}'