Skip to content

Commit

Permalink
Merge pull request #102 from replicate/webhook-validation-on-app-router
Browse files Browse the repository at this point in the history
add support for optionally requesting, receiving, and validating webhooks
  • Loading branch information
zeke authored May 13, 2024
2 parents 43856bc + 863a91a commit 50432ce
Show file tree
Hide file tree
Showing 7 changed files with 414 additions and 20 deletions.
12 changes: 12 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Find your Replicate API token at https://replicate.com/account/api-tokens
# REPLICATE_API_TOKEN=

# Use ngrok to expose your local server to the internet
# so the Replicate API can send it webhooks
#
# e.g. https://8db01fea81ad.ngrok.io
NGROK_HOST=

# Shared secret for verifying that incoming webhooks are sent by Replicate
# See https://replicate.com/docs/webhooks#retrieving-the-webhook-signing-key
REPLICATE_WEBHOOK_SIGNING_SECRET=
52 changes: 47 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
## Getting started with Next.js and Replicate

<img width="100%" alt="iguana" src="https://github.com/replicate/cog/assets/2289/1d8d005c-e4a1-4a9d-bd4c-b573fc121b37">

This is a [Next.js](https://nextjs.org/) template project that's preconfigured to work with Replicate's API.

It uses Next's newer [App Router](https://nextjs.org/docs/app) and [Server Components](https://nextjs.org/docs/app/building-your-application/rendering/server-components).
Expand All @@ -9,18 +11,25 @@ You can use this as a quick jumping-off point to build a web app using Replicate
## Noteworthy files

- [app/page.js](app/page.js) - The React frontend that renders the home page in the browser
- [app/api/predictions/routes.js](app/api/predictions/routes.js) - The backend API endpoint that calls Replicate's API to create a prediction
- [app/api/predictions/[id]/routes.js](pages/api/predictions/[id]/routes.js) - The backend API endpoint that calls Replicate's API to get the prediction result
- [app/api/predictions/route.js](app/api/predictions/route.js) - The backend API endpoint that calls Replicate's API to create a prediction
- [app/api/predictions/[id]/route.js](pages/api/predictions/[id]/route.js) - The backend API endpoint that calls Replicate's API to get the prediction result
- [app/api/webhooks/route.js](pages/api/predictions/[id]/route.js) - The backend API endpoint that calls Replicate's API to get the prediction result

## Usage
## Running the app

Install dependencies:

```console
npm install
```

Add your [Replicate API token](https://replicate.com/account#token) to `.env.local`:
Create a git-ignored text file for storing secrets like your API token:

```
cp .env.example .env.local
```

Add your [Replicate API token](https://replicate.com/account/api-tokens) to `.env.local`:

```
REPLICATE_API_TOKEN=<your-token-here>
Expand All @@ -36,4 +45,37 @@ Open [http://localhost:3000](http://localhost:3000) with your browser.

For detailed instructions on how to create and use this template, see [replicate.com/docs/get-started/nextjs](https://replicate.com/docs/get-started/nextjs)

<img width="707" alt="iguana" src="https://github.com/replicate/getting-started-nextjs/assets/14149230/5d1933ec-a083-4de6-90e2-7552e33e4a85">

## Webhooks

Webhooks provide real-time updates about your predictions. When you create a prediction or training, specify a URL that you control and Replicate will send HTTP POST requests to that URL when the prediction is created, updated, and completed.

This app is set up to optionally request, receive, and validate webhooks.

### How webhooks work

1. You specify a webhook URL when creating a prediction in [app/api/predictions/[id]/route.js](app/api/predictions/[id]/route.js)
1. Replicate sends POST requests to the handler in [app/api/webhooks/route.js](app/api/webhooks/route.js) as the prediction is updated.

### Requesting and receiving webhooks

To test webhooks in development, you'll need to create a secure tunnel to your local machine, so Replicate can send POST requests to it. Follow these steps:

1. [Download and set up `ngrok`](https://replicate.com/docs/webhooks#testing-your-webhook-code), an open-source tool that creates a secure tunnel to your local machine so you can receive webhooks.
1. Run ngrok to create a publicly accessible URL to your local machine: `ngrok http 3000`
1. Copy the resulting ngrok.app URL and paste it into `.env.local`, like this: `NGROK_HOST="https://020228d056d0.ngrok.app"`.
1. Leave ngrok running.
1. In a separate terminal window, run the app with `npm run dev`
1. Open [localhost:3000](http://localhost:3000) in your browser and enter a prompt to generate an image.
1. Go to [replicate.com/webhooks](https://replicate.com/webhooks) to see your prediction status.

### Validating incoming webhooks

Follow these steps to set up your development environment to validate incoming webhooks:

1. Get your signing secret by running:
```
curl -s -X GET -H "Authorization: Bearer $REPLICATE_API_TOKEN" https://api.replicate.com/v1/webhooks/default/secret
```
1. Add this secret to `.env.local`, like this: `REPLICATE_WEBHOOK_SIGNING_SECRET=whsec_...`
1. Now when you run a prediction, the webhook handler in [app/api/webhooks/route.js](app/api/webhooks/route.js) will verify the webhook.
19 changes: 16 additions & 3 deletions app/api/predictions/route.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@ const replicate = new Replicate({
auth: process.env.REPLICATE_API_TOKEN,
});

// In production and preview deployments (on Vercel), the VERCEL_URL environment variable is set.
// In development (on your local machine), the NGROK_HOST environment variable is set.
const WEBHOOK_HOST = process.env.VERCEL_URL
? `https://${process.env.VERCEL_URL}`
: process.env.NGROK_HOST;

export async function POST(request) {
if (!process.env.REPLICATE_API_TOKEN) {
throw new Error(
Expand All @@ -14,10 +20,17 @@ export async function POST(request) {

const { prompt } = await request.json();

const prediction = await replicate.predictions.create({
const options = {
version: '8beff3369e81422112d93b89ca01426147de542cd4684c244b673b105188fe5f',
input: { prompt },
});
input: { prompt }
}

if (WEBHOOK_HOST) {
options.webhook = `${WEBHOOK_HOST}/api/webhooks`
options.webhook_events_filter = ["start", "completed"]
}

const prediction = await replicate.predictions.create(options);

if (prediction?.error) {
return NextResponse.json({ detail: prediction.error }, { status: 500 });
Expand Down
31 changes: 31 additions & 0 deletions app/api/webhooks/route.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// The Replicate webhook is a POST request where the request body is a prediction object.
// Identical webhooks can be sent multiple times, so this handler must be idempotent.

import { NextResponse } from 'next/server';
import { validateWebhook } from 'replicate';

export async function POST(request) {
console.log("Received webhook...");

const secret = process.env.REPLICATE_WEBHOOK_SIGNING_SECRET;

if (!secret) {
console.log("Skipping webhook validation. To validate webhooks, set REPLICATE_WEBHOOK_SIGNING_SECRET")
const body = await request.json();
console.log(body);
return NextResponse.json({ detail: "Webhook received (but not validated)" }, { status: 200 });
}

const webhookIsValid = await validateWebhook(request.clone(), secret);

if (!webhookIsValid) {
return NextResponse.json({ detail: "Webhook is invalid" }, { status: 401 });
}

// process validated webhook here...
console.log("Webhook is valid!");
const body = await request.json();
console.log(body);

return NextResponse.json({ detail: "Webhook is valid" }, { status: 200 });
}
Loading

0 comments on commit 50432ce

Please sign in to comment.