Developer implementation reference

Build a FARPY webhook receiver

This page covers receiver construction, validation boundaries, event handling, acknowledgement, reconciliation, and safe operational behavior. For the shorter product-level delivery overview, use the API webhook guide.

Proven delivery contract

The current OpenAPI contract does not define a signature header name or cryptographic algorithm. Receiver code must therefore bind its verification step to the live FARPY documentation or contract rather than guessing a conventional header or HMAC format.

Configure delivery during upload

A render upload may include webhook_url and webhook_secret. The webhook URL must be a URI no longer than 2,048 characters and must resolve to an allowed HTTPS target. The optional secret must contain at least 16 characters. FARPY generates a secret when the caller omits it.

webhook_url=https://example.com/farpy/webhooks
webhook_secret=<at least 16 characters>

# webhook_secret is optional.
# FARPY generates one when it is omitted.

Render objects expose webhook_configured, which tells the authenticated caller whether terminal webhook delivery is configured for that render.

Receiver processing order

  1. Accept only the expected HTTPS POST route.
  2. Read and retain the exact raw request bytes.
  3. Apply a reasonable body-size limit before buffering indefinitely.
  4. Verify the published FARPY signature before trusting the payload.
  5. Reject malformed JSON and unsupported event shapes safely.
  6. Store enough information to detect repeated delivery.
  7. Acknowledge successful durable receipt quickly.
  8. Process expensive downstream work outside the request path.
  9. Reconcile canonical render status before irreversible actions.

Signature validation must occur against the raw body. Parsing and re-serializing JSON can alter whitespace or ordering and may destroy the exact byte representation required by the published signing contract.

Minimal Node.js receiver shape

import { createServer } from "node:http";

createServer(async (request, response) => {
  if (request.method !== "POST") {
    response.writeHead(405);
    response.end();
    return;
  }

  const chunks = [];

  for await (const chunk of request) {
    chunks.push(chunk);
  }

  const rawBody = Buffer.concat(chunks);

  // Verify the FARPY signature using the documented live
  // signature fields before parsing or trusting the payload.
  //
  // Do not invent a header name or algorithm. Bind this step
  // to the currently published FARPY webhook contract.

  const event = JSON.parse(rawBody.toString("utf8"));

  switch (event.type) {
    case "render.completed":
    case "render.failed":
    case "render.cancelled":
      // Store the event, acknowledge it, and reconcile the
      // canonical render status before irreversible work.
      break;
    default:
      // Ignore or quarantine unknown future event types.
      break;
  }

  response.writeHead(200);
  response.end("ok");
}).listen(3000);

The example deliberately leaves the signature-header and algorithm binding incomplete because those values are not proven by the current OpenAPI schema. Filling them with assumptions would create a receiver that appears complete but may reject valid FARPY deliveries or accept forged requests.

Terminal-event handling

render.completed

Treat completion as a signal to read canonical render status and then retrieve the artifact, receipt, and proof through authenticated API routes. Do not make an irreversible billing, publishing, or delivery decision from an unverified webhook body alone.

render.failed

Record the terminal failure and reconcile the job object. Separate permanent workload failure from webhook transport failure. A received failure event describes the render outcome; it does not mean the webhook receiver itself failed.

render.cancelled

Record cancellation as a distinct terminal result. FARPY cancellation is supported before worker claim, so downstream systems should not treat cancelled and failed jobs as interchangeable.

Idempotent consumer design

Durable delivery means a receiver must tolerate repeated attempts. Store a stable event identifier when one is provided by the live payload contract. When no proven identifier is available, combine canonical render identity, terminal event type, and canonical status reconciliation to prevent duplicate downstream effects.

The safe rule is simple: receiving the same terminal meaning twice must not send two customer messages, publish two artifacts, or trigger two accounting actions. Acknowledge delivery separately from completing business processing.

Network and security boundaries

FARPY refuses private-network webhook targets and does not follow redirects. These controls reduce server-side request-forgery risk and prevent an allowed public URL from redirecting delivery toward an internal service. The receiver should apply its own TLS, access-log, request-size, timeout, secret-storage, and incident-response controls.

Never place the signing secret in browser code, public logs, source control, analytics, or error pages. Rotate a compromised secret by creating future webhook-configured work with a replacement secret and updating the receiver verification configuration.

Testing checklist

Frequently asked questions

Which events should the receiver accept?

The proven events are render.completed, render.failed, and render.cancelled.

Can the target redirect FARPY elsewhere?

No. Redirect following is disabled.

Can the target use an internal IP address?

No. Private-network webhook targets are blocked.

Does FARPY generate a signing secret?

Yes. FARPY generates one when webhook_secret is omitted. A supplied secret must contain at least 16 characters.

Where is the exact signature format documented?

Use the current live FARPY contract. The present OpenAPI schema proves that delivery is signed but does not itself define the header name or cryptographic algorithm.

Webhook overview · Developer reference · Error handling · Capabilities · OpenAPI