API & webhooks
Send an HTTP JSON Body in Node.js with the Correct Byte Length
Build a Node HTTP POST using encoded JSON bytes, finish the request, inspect the returned body, and distinguish inactivity from a total timeout.
Prepare the payload before opening the request
With Node's lower-level HTTP client, the request body is a sequence of bytes you write to a stream. Serialize the JSON value first, encode it as UTF-8 and measure that encoded buffer. JavaScript string length counts UTF-16 code units, so it is not a reliable Content-Length for text containing accented characters or emoji.
The Node request API exposes the request object before a response is available. Attach error handling before sending data, finish the request deliberately and consume the response. A response callback alone does not mean the server has finished sending its body.
Send one JSON POST and retain its response status
const http = require("node:http");
const https = require("node:https");
function sendJson(urlText, value) {
const url = new URL(urlText);
const client = url.protocol === "https:" ? https
: url.protocol === "http:" ? http : null;
if (!client) throw new Error("Use an HTTP or HTTPS URL.");
const json = JSON.stringify(value);
if (json === undefined) throw new Error("A JSON value is required.");
const payload = Buffer.from(json, "utf8");
return new Promise((resolve, reject) => {
const request = client.request(url, {
method: "POST",
headers: {
"Content-Type": "application/json",
"Content-Length": payload.length,
Accept: "application/json"
}
}, async response => {
try {
const chunks = [];
let bytes = 0;
for await (const chunk of response) {
bytes += chunk.length;
if (bytes > 1048576) throw new Error("Response is too large.");
chunks.push(chunk);
}
resolve({status: response.statusCode,
text: Buffer.concat(chunks).toString("utf8")});
} catch (error) { reject(error); }
});
request.on("error", reject);
request.setTimeout(5000, () => request.destroy(new Error("Socket inactivity timeout.")));
request.end(payload);
});
}
Save this CommonJS example in a .cjs file or adapt its imports to your application's module format. The payload is written by request.end, which also marks the outgoing message complete. Calling request.write without eventually ending the request can leave the server waiting for additional input.
Verify the actual bytes received by the server
For a value containing the text café and an emoji, compare the UTF-8 buffer length with the string length. They differ. A local fixture should count the received bytes, compare them with Content-Length and parse the body independently. That verifies transport framing and serialization together.
Do not manually copy a length from an earlier request after changing the payload. A stale length can truncate a message or leave a connection waiting for bytes that never arrive. When using a higher-level client such as fetch, normally let that client handle framing instead of supplying your own length.
Keep application decisions outside the transport helper
The helper returns the status and UTF-8 body text for both success and HTTP error responses. Its caller must interpret a 201, a 204, a 409 or another status according to the API contract. An empty response body is not automatically a parsing failure because the helper does not assume every response is JSON.
The one-mebibyte response limit prevents this small example from collecting an unbounded body. It does not impose a request-body limit; validate the size and shape of the value before invoking it when the application accepts arbitrary input. Serialization can also reject circular structures before any request is sent.
The socket timeout reports inactivity. It is not a complete wall-clock deadline, and a timeout does not prove that a write had no effect. Do not add automatic retries to this helper without the endpoint's documented idempotency strategy and a bounded retry policy.
Keep destinations and credentials under application control
Use HTTPS for a real service carrying sensitive data. The HTTP option is useful for a local controlled fixture, but it does not provide transport encryption. The example does not include credentials, follow redirects or disable certificate verification.
If a backend accepts a destination URL from an untrusted user, selecting HTTP or HTTPS is not a sufficient destination policy. Restrict the services that application is intended to contact and handle redirects under the same policy. This transport example assumes its caller has already chosen an appropriate endpoint.
Test two different Unicode payloads and confirm that the second request sends its own bytes. Include an empty success response, an error response and a deliberately oversized response. Use the REST API Testing Tool for separate authorized browser checks while keeping byte-level assertions in the Node fixture.
Also close the fixture's connection partway through a declared response body and confirm that the promise rejects. A truncated JSON-looking string must not be mistaken for a complete result. Preserve the original error for controlled diagnostics while avoiding logs that include the full submitted note or any authentication headers.


