Webhooks
Sweep POSTs signed lifecycle events to the callback URL configured for the
partner. Delivery is at-least-once and ordered per attempt. Webhooks are
wake-up signals; GET /status remains canonical.
Events
wallet_submitted
provider_pending
settled
failedThere is intentionally no expired event. Keep status polling as the
source-of-truth fallback.
Delivery
Headers:
X-Partner-Id: partner-example
X-Timestamp: 1893456030000
X-Signature: <lowercase HMAC-SHA256 hex>
X-Sweep-Event-Id: evt_00000000000000000001
X-Sweep-Api-Version: 1
Content-Type: application/jsonBody — an attempt snapshot plus event metadata (synthetic example):
{
"eventId": "evt_00000000000000000001",
"event": "settled",
"occurredAt": "2030-01-01T00:05:00.000Z",
"attemptId": "attempt_00000000000000000001",
"intentSessionId": "intent_00000000000000000001",
"partnerId": "partner-example",
"status": "settled",
"currentStatus": "settled",
"walletAddress": "0x1111111111111111111111111111111111111111",
"recipient": "0x1111111111111111111111111111111111111111",
"submittedAt": 1893456030000,
"perChainStatus": [],
"settledOutputs": [
{
"chainId": 42161,
"symbol": "USDC",
"amount": "9.950000000000000000",
"amountBasis": "estimated",
"txHash": "0xdddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd"
}
]
}Signature
Webhooks intentionally use the frozen exact-body signature even when partner requests use auth v2:
X-Signature = HMAC_SHA256(PARTNER_SECRET, "<X-Timestamp>.<exact raw JSON body>")Processing rules
- Capture the exact raw request bytes before any JSON parsing.
- Verify partner id, timestamp (±5 minutes) and signature against the raw bytes.
- Verify
X-Sweep-Event-Idequals payloadeventId. - Durably deduplicate
eventId; commit the event before acknowledging. - Return 2xx only after verification and persistence succeed; non-2xx triggers redelivery.
- Process a duplicate delivery as a no-op after the dedupe check.
Code
import express from 'express';
import { verifyWebhook } from 'sweep-external-sdk/server';
const app = express();
app.post('/webhooks/sweep', express.raw({ type: '*/*' }), async (req, res) => {
const result = verifyWebhook(
{ partnerId: process.env.SWEEP_PARTNER_ID, secret: process.env.SWEEP_PARTNER_SECRET },
{ rawBody: req.body, headers: req.headers }
);
if (!result.ok) return res.status(result.status).json({ error: result.error });
const isNew = await store.recordEventOnce(result.payload.eventId, result.payload);
if (isNew) await onAttemptEvent(result.payload); // e.g. trigger a /status poll
return res.status(200).end();
});The full verifyWebhook implementation is in
Reference code.
Last updated on