Bulk job completion callback
Geobridge can notify you when a bulk job finishes processing. Every delivery includes authentication headers so you can verify authenticity and defend against replays before acknowledging the request.
Headers to verify
Section titled “Headers to verify”| Header | Purpose |
|---|---|
X-Geobridge-Signature | Base64-encoded HMAC SHA-256 signature. |
X-Geobridge-Timestamp | Unix epoch seconds. Reject payloads with a stale timestamp. |
The signature is generated with the shared secret you supplied when creating the job. Geobridge signs the canonical string "<timestamp>." + body.
Verification flow
Section titled “Verification flow”- Parse the JSON body as bytes (do not mutate or prettify it).
- Confirm the timestamp is within your allowed replay window (5 minutes is a good default).
- Recompute
HMAC_SHA256(secret, canonicalString)and Base64-encode the digest. - Compare the signature with a constant-time function. Reject mismatches with a non-2xx status to trigger retries.
import crypto from 'node:crypto';
export function verifyWebhook({ signature, timestamp, rawBody, secret }) { const ts = Number(timestamp); if (!Number.isFinite(ts)) throw new Error('Invalid webhook timestamp');
const age = Math.abs(Date.now() / 1000 - ts); if (age > 300) throw new Error('Webhook timestamp outside allowable window');
const canonical = `${timestamp}.${rawBody}`; const expected = crypto .createHmac('sha256', secret) .update(canonical) .digest('base64');
const actualBuf = Buffer.from(signature, 'utf8'); const expectedBuf = Buffer.from(expected, 'utf8'); if (actualBuf.length !== expectedBuf.length) { throw new Error('Invalid webhook signature length'); } if (!crypto.timingSafeEqual(actualBuf, expectedBuf)) { throw new Error('Invalid webhook signature'); }}import base64import hashlibimport hmacimport time
WINDOW_SECONDS = 300
def verify_webhook(signature: str, timestamp: str, body: bytes, secret: str) -> None: now = int(time.time()) ts = int(timestamp) if abs(now - ts) > WINDOW_SECONDS: raise ValueError("Webhook timestamp outside allowable window")
canonical = f"{timestamp}.".encode() + body digest = hmac.new(secret.encode(), canonical, hashlib.sha256).digest() expected = base64.b64encode(digest).decode()
if not hmac.compare_digest(signature, expected): raise ValueError("Signature mismatch")Retry behaviour
Section titled “Retry behaviour”- Geobridge retries with exponential backoff using the policy you set on the job (
max_attempts,strategy,initial_delay_seconds). - Return an
HTTP 2xxstatus once the payload is persisted. Non-2xx responses consume a retry. - If retries exhaust, the job status remains
failedand the error is captured in the job metadata for later inspection.