Node 26.10 SQLite binds undefined as NULL: a Sume webhook job table
Node 26.10 binds undefined as NULL in node:sqlite. A Sume job-webhook handler can still be explicit with null and dedupe on job_id without a driver.

If you store Sume webhook events with node:sqlite, write null yourself and do not lean on undefined. The Node.js 26.10.0 release notes say SQLite now binds undefined as NULL (Node.js 26.10.0 release notes, read 2026-10-04). On Node 22.14 the same call throws Provided value cannot be bound to SQLite parameter, which I confirmed by running the script below. A handler that works on your laptop on one Node line can therefore fail on a server running another.
A job webhook is a natural place to hit this. A job.completed event carries payload.artifacts, and a job.failed event carries an error object instead, so one of the two optional columns is always missing. Optional chaining then yields undefined, which is exactly the value whose binding changed.
What the job events contain
Sume sends terminal job events only: job.completed, job.failed and job.canceled. Each body has event, request_id, job_id, status and either a payload with artifacts or an error object (Webhooks). The docs tell you to use job_id as the idempotency key on your side, since a delivery can repeat, and to return any 2xx after durably storing the event.
That maps to a table with job_id as the primary key and an insert that does nothing on conflict. The return value of run() then tells you whether this delivery was new.
| Webhook field | Column | When missing |
|---|---|---|
| job_id | job_id (primary key) | Never; reject the delivery |
| event | event | Never |
| payload.artifacts[0].url | artifact_url | null on failed and canceled |
| error.code | error_code | null on completed |
A handler body that runs on Node 22 and 26
The function below parses the raw body after your signature check, flattens the optional fields with ?? null, and returns true only for a first delivery. It uses an in-memory database so you can run it as a test; swap in a file path for real use.
import { DatabaseSync } from "node:sqlite";
const db = new DatabaseSync(":memory:");
db.exec(`CREATE TABLE IF NOT EXISTS sume_jobs (
job_id TEXT PRIMARY KEY,
event TEXT NOT NULL,
artifact_url TEXT,
error_code TEXT,
received_at INTEGER NOT NULL
)`);
const upsert = db.prepare(`INSERT INTO sume_jobs (job_id, event, artifact_url, error_code, received_at)
VALUES (?, ?, ?, ?, ?) ON CONFLICT(job_id) DO NOTHING`);
export function record(body) {
const e = JSON.parse(body);
const art = e.payload?.artifacts?.[0];
const r = upsert.run(e.job_id, e.event, art?.url ?? null, e.error?.code ?? null, Date.now());
return r.changes === 1;
}
console.log(record('{"event":"job.completed","job_id":"job_1","payload":{"artifacts":[{"url":"https://media.sume.com/artifacts/a"}]}}'));
console.log(record('{"event":"job.completed","job_id":"job_1"}'));
console.log(db.prepare("SELECT * FROM sume_jobs").all());Operational notes
- Verify the signature on the raw body before you parse or insert anything. The SDK exports
verifyWebhookfor that. - Reply 2xx only after the insert commits. A non-2xx reply is retried, up to 10 attempts at a fixed 30-second spacing.
- Keep polling
GET /v1/jobs/{id}as the backstop for a delivery that never arrives. - Pin the Node version in your container so the binding behavior you tested is the one you run.
Sources
Related posts
More in Developers
- OpenAI Agents API hosted sandbox: which Sume hosts to allow
OpenAI's Agents API is in public beta with hosted or connected sandboxes. Which Sume hosts to allow, how to pass the MCP URL, and why the key stays in a secret.
- OpenAI Agents API sandbox: keep the Sume API key out of it
The OpenAI Agents API beta adds a sandbox and hosted-browser computer use. Where a Sume API key can live when an agent runs there, and what to hand it instead.
- OpenAI Python 3.23 turn artifacts: hand over a Sume render
OpenAI Python SDK 3.23.0 adds file staging and turn artifact downloads for agents. Sume job webhooks give you a public media.sume.com URL to hand over.
- OpenAI Realtime GA migration: drop the OpenAI-Beta header
Beta Realtime integrations must move to GA and stop sending OpenAI-Beta: realtime=v1. A short checklist, a code scan for the header, and where Sume jobs fit.
Written by Sume