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 })
}
@borispoehland
Copy link
Author

borispoehland commented Feb 14, 2024

  1. Create a new POST API route with above code

  2. Set the following 2 environment variables in your project:

  • VERCEL_TEAM_ID: Found under Team > Settings > General > Team Id
  • VERCEL_TOKEN: Found under Settings > Tokens > Create a new token

If you use the pause_all snippet, all projects of your VERCEL_TEAM_ID will be paused. If you want to hardcode the project to pause only a single project, use the pause_single snippet and additionally set

  • VERCEL_PROJECT_ID: Found in your Project > Settings > General > Project Id
  1. Go to Team > Settings > Billing > Spend Management, enable it, set a limit (e.g. 100$) and provide the full URL to POST API route you created

Now that your project is paused, you can resume it at any time from the dashboard. Note that this is a hard switch, if you don't want to pause the project you can run more graceful logic

@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