Reuse the fetchWithPayment helper you built during the quickstart so Coinbase x402 invoices are
handled automatically.
1. Kick off the crawl
const baseUrl = process.env.HORIZON_BASE_URL ?? 'https://api.horizon.new/v1';
const crawlResponse = await fetchWithPayment(`${baseUrl}/crawl`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
url: 'https://docs.horizon.new/api-reference/endpoint',
depth: 1,
priority: 'high',
notify: {
url: 'https://example.com/webhooks/crawl',
events: ['completed'],
},
}),
});
const crawlJob = await crawlResponse.json();
console.log('crawl', crawlJob.crawlId, crawlJob.jobId, crawlJob.statusUrl);
The payload includes both the crawl identifier and a statusUrl for job polling.
2. Handle synchronous completion
if (crawlJob.status === 'completed' && crawlJob.result) {
console.log('Captured pages immediately', crawlJob.result.pages.length);
// You can skip polling if the crawl finished inline.
}
3. Poll until the crawl settles
let status;
do {
status = await fetchWithPayment(crawlJob.statusUrl).then((res) => res.json());
if (status.state === 'processing') {
console.log('Still walking links...', status.progress?.percentComplete ?? 0);
await new Promise((resolve) => setTimeout(resolve, 5000));
}
} while (status.state === 'processing');
if (status.state !== 'succeeded') {
throw new Error(`Crawl failed: ${status.error?.code ?? 'unknown'}`);
}
console.log('Captured pages', status.result.pages.length);
4. React to the webhook (optional)
import crypto from 'node:crypto';
const verifyWebhookSignature = (signature: string, payload: unknown) => {
const secret = process.env.HORIZON_WEBHOOK_SECRET!;
const computed = crypto
.createHmac('sha256', secret)
.update(JSON.stringify(payload))
.digest('hex');
return crypto.timingSafeEqual(Buffer.from(signature, 'hex'), Buffer.from(computed, 'hex'));
};
app.post('/webhooks/crawl', async (req, res) => {
const signature = req.header('X-Horizon-Signature') ?? '';
if (!verifyWebhookSignature(signature, req.body)) {
return res.status(401).end();
}
const { jobId, state, result } = req.body;
if (state === 'succeeded') {
console.log('Crawl completed', jobId, result.pages.length);
}
res.status(204).end();
});
Webhook callbacks arrive once per event you subscribe to; pair them with short polling windows if
you need rapid feedback during the first few seconds of a crawl.