Shopify and Stripe connectors
Turn verified Shopify orders and Stripe billing events into trusted loyalty events, and let customers spend store credit at Shopify checkout.
A connector links one Shopify shop or one Stripe account to an Orishare environment. The
provider sends its webhooks straight to Orishare; every delivery is checked against its
signature before anything is read, and the events it produces carry the strongest trust level,
verified_evidence, so rules can require proof instead of a merchant assertion.
1. Install a connector
Secrets are write-only: they are stored encrypted and no response, list or audit record ever returns them.
# Shopify: the shop domain, an Admin API access token (for discount codes) and the app's
# client secret, which Shopify uses to sign webhooks.
curl -X POST "$ORISHARE_API/v1/connectors" -H "Authorization: Bearer $SECRET_KEY" \
-H "Idempotency-Key: shopify-connector" -H "Content-Type: application/json" \
-d '{"provider":"shopify","external_account":"example.myshopify.com",
"credentials":{"access_token":"shpat_..."},"webhook_secret":"<app client secret>",
"config":{"shop_currency":"USD"}}'
# Stripe: the account id and the signing secret of the webhook endpoint (whsec_...).
curl -X POST "$ORISHARE_API/v1/connectors" -H "Authorization: Bearer $SECRET_KEY" \
-H "Idempotency-Key: stripe-connector" -H "Content-Type: application/json" \
-d '{"provider":"stripe","external_account":"acct_123","webhook_secret":"whsec_...",
"config":{"customer_mapping":{"mode":"metadata","field":"orishare_customer_id"}}}'The response carries webhook_path, for example
/v1/connectors/shopify/cin_.../webhook. Register https://api.orishare.com followed by that
path as the webhook address at the provider:
- Shopify: subscribe to
orders/paidandrefunds/create(JSON format).config.shop_currencyis required: the shop's currency (ISO 4217), in which every discount amount is expressed. Each connector needs its own Shopify custom app: Orishare verifies a delivery by the app's signing secret, so one secret can belong to exactly one connector, in any environment, for good: a secret stays bound to its connector after it is rotated out or the connector is deleted. Create a separate custom app for your test environment, on a Shopify development store, and never reuse the production app's secret. The delivery must also name the connector's own shop. - Stripe: add an endpoint for
invoice.paid,customer.subscription.created,customer.subscription.deleted,payment_intent.succeededandcharge.refunded.
2. Which customer an event belongs to
config.customer_mapping decides how a payload names the Orishare customer:
| Mode | Shopify | Stripe |
|---|---|---|
provider_customer_id (default) | the order's customer id becomes the external id | the customer id (cus_...) becomes the external id |
metadata with field | the order's note attribute named field | the object's metadata[field] |
A delivery whose customer cannot be found is acknowledged and recorded as unmapped_customer.
A Shopify refund belongs to the customer of its order.
3. Events you can write rules on
| Provider topic | Orishare event | Main properties |
|---|---|---|
Shopify orders/paid | order.completed | order_id, amount_minor, currency, discount_codes |
Shopify refunds/create | order.refunded | order_id, refund_id, amount_minor, currency |
Stripe invoice.paid | subscription.renewed | invoice_id, subscription_id, amount_minor, currency |
Stripe customer.subscription.created | subscription.started | subscription_id, status, price_id |
Stripe customer.subscription.deleted | subscription.cancelled | subscription_id, cancellation_reason |
Stripe payment_intent.succeeded | payment.succeeded | payment_intent_id, amount_minor, currency |
Stripe charge.refunded | payment.refunded | charge_id, amount_minor (total refunded so far), currency |
Amounts are integer minor units (cents). Each delivery becomes at most one event: the provider's
retries of the same event are acknowledged and ignored. Only what the provider signed decides
what a delivery means: an orders/paid delivery whose order Shopify does not report as paid,
or whose body is not an order, is acknowledged and ignored (ignored_topic_mismatch). A refund
that arrives before its order is answered 503 so Shopify delivers it again later.
4. Store credit at Shopify checkout
With a store credit wallet, a customer can turn credit into a single-use Shopify discount code:
curl -X POST "$ORISHARE_API/v1/connectors/$CONNECTOR_ID/shopify/redemptions" \
-H "Authorization: Bearer $SECRET_KEY" -H "Idempotency-Key: redeem-1" \
-H "Content-Type: application/json" \
-d '{"customer_id":"cust_...","currency":"credit","amount":1500}'The credit is held at once and the response returns the code. When the orders/paid webhook
of the order that used the code arrives, the hold is captured (any part the order did not use
returns), also for guest checkouts. The code expires when the hold does (hold_until on the
application), and a stale hold is released by POST /v1/store_credit/release_stale. The wallet
must be in the shop currency.
If Shopify refuses to create the code, the credit returns immediately and the request fails
with reward_unavailable. If Shopify's answer is lost, Orishare looks the code up once: found,
the redemption stands; absent, the credit returns. If that lookup fails too, the response is
202 with status: "pending_confirmation" and the credit stays held; call
POST /v1/connectors/{id}/shopify/redemptions/{code}/reconcile later to settle it.
A hold behind a discount code is not released by release_stale. Call
POST /v1/connectors/{id}/shopify/redemptions/expire (for example hourly). For each hold that
ended more than an hour ago it deactivates the code at Shopify first, then reads its usage.
Shopify counts usage asynchronously, so a zero count is never final on its own: the response
lists the hold under awaiting_confirmation, and the credit returns only when a read at least
24 hours later is still zero and a search of the shop's orders finds no order that used the
code. A used code stays held until its paid order's webhook captures it, and a code once seen
used is never refunded later, even if the discount is deleted at Shopify. If Shopify cannot be
reached or answers unclearly, the hold stays and a later call tries again. Two calls at the same
moment never check the same code twice.
The order search needs the read_orders scope on the custom app. Without read_all_orders
Shopify searches only the last 60 days of orders, which covers the default hold durations. A
code issued more than 59 days before the search is never refunded on an empty search: the
expiry lists its hold under needs_reconcile, and it stays held until you settle it with
POST /v1/connectors/{id}/shopify/redemptions/{code}/reconcile after checking the shop's orders.
POST /v1/connectors/{id}/shopify/discount_codes issues a plain fixed amount code, for
example as a reward.
5. Operate a connector
GET /v1/connectors/{id}/receiptslists recent deliveries with their outcome:processed,ignored,unmapped_customerorrejected.PATCH /v1/connectors/{id}changes the config or status, or replaces the credentials or the webhook secret (rotate at the provider first, then here).DELETE /v1/connectors/{id}disables the connector and erases its stored secrets. Deliveries to a disabled connector are refused.
A delivery that cannot be verified (unknown connector, wrong signature, another shop, a
compressed body, Stripe timestamp older than five minutes, disabled connector) is answered
401 with the same message every time.
Birthdays and anniversaries
Collect a birthday, an anniversary or any yearly date for a customer and reward it once a year, at a local time in the customer's time zone.
Klaviyo connector
Keep every customer's Klaviyo profile current with points, VIP tier, birthday, referral code and store credit, and send loyalty activity to Klaviyo as metrics for flows.