Store Entries API
Store Entries
Shared Sweeps API · Partner Integration
Stores giveaway entries directly in the database for a customer. This is a headless, write-only endpoint for a trusted server-to-server partner integration — it does not require the Shopify app to be installed and does not read from Shopify.
https://api.sharedsweepsapp.com
/store-entries
Authenticates the caller via an HMAC signature, then records the entry
in the customerEntriesTx ledger. Each shop gets a dedicated
giveaway (slug api-entries) created on first use and reused
permanently, since the entry's giveaway foreign key cannot be null.
(orderID, customerID, giveawayID) —
an existing match updates entriesEarned (200); otherwise a new row is inserted (201). When orderId is omitted, a unique
manual-<uuid> is generated, so those calls always
insert. The customerEntriesWallet aggregate is not updated
by this endpoint.
Authentication
| Header | Required | Description |
|---|---|---|
X-Timestamp |
required | Unix time in seconds when the request was signed. |
X-Signature |
required |
Lowercase hex
HMAC-SHA256(secret, "<X-Timestamp>.<rawBody>").
|
Content-Type |
required | application/json |
The signed payload is the timestamp and the exact raw
body joined by a literal . —
`${timestamp}.${rawBody}`. Send the same byte string you
signed; do not re-serialize the JSON. The timestamp must be within
±5 minutes of server time, and the signature is checked
against every active secret (enabling zero-downtime rotation). Any
failure returns 401 without
revealing which check failed.
const crypto = require("crypto");
const body = JSON.stringify({ customerId, entriesEarned, shop, orderId });
const timestamp = Math.floor(Date.now() / 1000).toString();
const signature = crypto
.createHmac("sha256", process.env.SHARED_SECRET)
.update(`${timestamp}.${body}`)
.digest("hex");
await fetch("https://api.sharedsweepsapp.com/store-entries", {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-Timestamp": timestamp,
"X-Signature": signature,
},
body, // send this EXACT string — re-serializing breaks the signature
});Request Body
| Field | Type | Required | Description |
|---|---|---|---|
customerId |
string | number | required |
Customer identifier. Accepts the numeric ID or
gid://shopify/Customer/<id>; the GID prefix is
stripped and the numeric form is stored.
|
entriesEarned |
number | required | Number of entries to record. Numeric strings are coerced. |
orderId |
string | number | optional |
Order identifier. Accepts the numeric ID or
gid://shopify/Order/<id>; the GID prefix is
stripped and the numeric form is stored. When
omitted, a synthetic manual-<uuid> is generated.
|
shop |
string | optional* |
Shop domain, e.g. store.myshopify.com. *Required
unless sent via the X-Shopify-Shop-Domain header.
|
POST /store-entries
Content-Type: application/json
X-Timestamp: 1749945600
X-Signature: 3a7bd3e2360a3d29eea436fcfb7e44c735d117c42d1c1835420b6b9942dd4f1b
{
"customerId": "1234567890",
"entriesEarned": 25,
"orderId": "gid://shopify/Order/5555555555",
"shop": "store.myshopify.com"
}Response
{
"success": true,
"updated": false,
"entry": {
"id": 9876,
"orderID": "gid://shopify/Order/5555555555",
"customerID": "1234567890",
"entriesEarned": 25,
"giveawayID": 42,
"shop": "store.myshopify.com"
}
}| Field | Type | Description |
|---|---|---|
success |
boolean | Always true on a 2xx response. |
updated |
boolean |
true if an existing entry was updated, false if a new one inserted. |
entry.id |
number | Primary key of the stored customerEntriesTx row. |
entry.orderID |
string | Order ID stored (provided value or synthesized manual-<uuid>). |
entry.customerID |
string | The customer identifier. |
entry.entriesEarned |
number | The entries recorded. |
entry.giveawayID |
number | The dedicated api-entries giveaway ID for the shop. |
entry.shop |
string | The shop domain the entry belongs to. |
| Status | Body | Cause |
|---|---|---|
| 400 | { "error": "Invalid JSON body" } |
Request body is not valid JSON. |
| 400 | { "error": "Validation failed", "details": {…} } |
Missing/invalid customerId or entriesEarned. |
| 400 | { "error": "Missing shop (…)" } |
No shop in body or header. |
| 401 | { "error": "Unauthorized" } |
Missing/invalid signature, stale timestamp, or no active secret. |
| 500 | { "error": "Internal server error" } |
Unexpected failure (reported to Sentry). |