Skip to content

Instantly share code, notes, and snippets.

@borispoehland
Last active June 8, 2024 13:44
Show Gist options
  • Save borispoehland/2738d2edd62e83f332260a80eb5a9335 to your computer and use it in GitHub Desktop.
Save borispoehland/2738d2edd62e83f332260a80eb5a9335 to your computer and use it in GitHub Desktop.
Vercel Webhook to call to pause the project after the spending limit is reached
import crypto from 'crypto'
const { INTEGRATION_SECRET, VERCEL_TEAM_ID, VERCEL_TOKEN } = process.env
function sha1(data: Buffer, secret: string): string {
return crypto.createHmac('sha1', secret).update(data).digest('hex')
}
export async function POST(request: Request) {
if (typeof INTEGRATION_SECRET != 'string') {
throw new Error('No integration secret found')
}
const rawBody = await request.text()
const rawBodyBuffer = Buffer.from(rawBody, 'utf-8')
const bodySignature = sha1(rawBodyBuffer, INTEGRATION_SECRET)
if (bodySignature !== request.headers.get('x-vercel-signature')) {
return Response.json({
code: 'invalid_signature',
error: "signature didn't match",
})
}
const { projects } = (await fetch(
`https://api.vercel.com/v9/projects?teamId=${VERCEL_TEAM_ID}`,
{
headers: {
Authorization: `Bearer ${VERCEL_TOKEN}`,
},
}
).then((res) => res.json())) as { projects: { id: string }[] }
let isSuccess = true
for (const project of projects) {
const pauseProject = await fetch(
`https://api.vercel.com/v1/projects/${project.id}/pause?teamId=${VERCEL_TEAM_ID}`,
{
headers: {
Authorization: `Bearer ${VERCEL_TOKEN}`,
},
method: 'POST',
}
)
if (!pauseProject.ok) {
// send me a backup notification to my email
console.error(pauseProject.statusText)
isSuccess = false
}
}
return Response.json({ code: 'paused_all', data: isSuccess })
}
// /api/project/pause/route.ts
import crypto from 'crypto'
const { INTEGRATION_SECRET, VERCEL_PROJECT_ID, VERCEL_TEAM_ID, VERCEL_TOKEN } =
process.env
function sha1(data: Buffer, secret: string): string {
return crypto.createHmac('sha1', secret).update(data).digest('hex')
}
export async function POST(request: Request) {
if (typeof INTEGRATION_SECRET != 'string') {
throw new Error('No integration secret found')
}
const rawBody = await request.text()
const rawBodyBuffer = Buffer.from(rawBody, 'utf-8')
const bodySignature = sha1(rawBodyBuffer, INTEGRATION_SECRET)
if (bodySignature !== request.headers.get('x-vercel-signature')) {
return Response.json({
code: 'invalid_signature',
error: "signature didn't match",
})
}
const pauseProject = await fetch(
`https://api.vercel.com/v1/projects/${VERCEL_PROJECT_ID}/pause?teamId=${VERCEL_TEAM_ID}`,
{
headers: {
Authorization: `Bearer ${VERCEL_TOKEN}`,
'Content-Type': 'application/json',
},
method: 'POST',
}
)
if (!pauseProject.ok) {
// send me a backup notification to my email
console.error(pauseProject.statusText)
}
return Response.json({ code: 'paused', data: pauseProject.ok })
}
@Vkzem
Copy link

Vkzem commented Feb 15, 2024

Thanks for this - was paranoid after the twitter drama of runaway spend and found this right away :)

@Maxservais
Copy link

Thank you! Do you know if there is any "easy" way to test it works?

@marcelmeulemans
Copy link

Nice, thanks! Used this to write a small repo that can be directly deployed separately on Vercel.

@naeem-qv
Copy link

Do we still need to manually pause/resume projects or is it automatically handled now??
I'm told its automatic now by some YouTuber.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment