curl -d @file strips newlines: a Sume webhook test that fails 401

Signing a fixture file and sending it with curl -d changes the bytes. Use --data-binary to test a Sume webhook receiver. Both signatures shown.

4 min readSume
All posts

You sign fixture.json with openssl, post it with curl -d @fixture.json, and your receiver says the signature is wrong. The receiver is fine. curl -d @file strips carriage returns and newlines from the file contents, so the body that arrives is not the body you signed. Sume signs the exact raw bytes (<timestamp>.<raw_body>), and one missing trailing newline is a different HMAC.

On curl 8.7.1, a 45-byte fixture ending in a newline arrived as 44 bytes with -d @fixture.json and as 45 bytes with --data-binary @fixture.json.

Same JSON, two byte strings, two signatures (read 2026-10-07)
Body signedBytesSignature (hex, timestamp 1790000000, secret whsec_test_123)
With trailing newline459d95a169d5504e9d88aa0e1fe205e082b6a7fdc2bdfc6336831afdd5e565f83f
Without it445ba7a215439d4b856d34fa60b0bd467778efa0d98eca14f659fcb5e74b1ca1f1

A sign-and-send script that works

This hashes the file bytes with cat, then sends the same file with --data-binary. Pointed at a Go receiver that verifies the header, it returned 200; swapping --data-binary for -d returned 401. Use a throwaway secret and your own endpoint; never put a real signing secret in a script that gets committed.

#!/usr/bin/env bash
set -eu
: "${SECRET:?set SECRET}"
TS=1790000000
SIG=$(printf '%s.' "$TS" | cat - fixture.json | openssl dgst -sha256 -hmac "$SECRET" | sed 's/.*= //')
curl -s -o /dev/null -w '%{http_code}\n' --data-binary @fixture.json \
  -H "x-sume-webhook-timestamp: $TS" -H "x-sume-webhook-signature: sume-v1=$SIG" \
  "http://localhost:8894/hook"

Related traps

jq . and jq -c rewrite whitespace, so pipe through them before signing, not after. Editors that add a final newline on save will change a fixture without telling you. When a mismatch is baffling, print the byte count on both sides (wc -c here, len(body) there) before suspecting the secret.

On the receiving side, verify against the raw request bytes, before any JSON parser re-serializes them. The related posts cover that for Node, Go and Bun.

Sources

Related posts

More in Developers

All Developers posts

Written by Sume