S
supero.docs
Documentation/Foundations/External Integrations

External Integrations

Payments, email, SMS, object storage, calendars and LLM keys, invoked through one endpoint — with the credentials held in the platform keystore instead of your application code.

An application invoking managed integrations through one execute endpoint, with credentials held in the keystore
Your application names an operation. The credential stays in the keystore, and the provider talks back over a webhook.

What It Is For

An application stops being a demo at the point where it has to charge a card, send a receipt, text a reminder, or put a file somewhere durable. Each of those is a provider account, a client library, a credential, a webhook endpoint and a retry policy — and each one traditionally lands in your codebase as a few hundred lines of glue that nobody enjoys owning.
Managed integrations move that boundary. The provider account stays yours: you sign up with Stripe, SendGrid or Twilio, and you are on the hook for their terms and their bill. What changes is where the credential lives and who writes the client. The secret goes into the platform keystore, the provider client lives in a service plugin, and your application invokes a named operation instead of importing an SDK.
Ten integrations are covered — transactional email, card payments, SMS, object storage, calendars, push notifications and LLM API keys. The list is short on purpose: every entry has working applications behind it. A provider that is not on it is an integration project, not a configuration step.

The Shape

There are two halves to an integration, and keeping them separate is the point. The application declares which integrations it needs and which configuration keys each expects. The credential is supplied separately, per project, after deploy — so the same bundle ships to a sandbox and to production without a secret moving with it.
Every integration is addressed by a service_id and has an exact set of config_keys. These are the ten, with the number of applications in the verified corpus using each:
service_idAppsconfig_keys
email (SendGrid)146sendgrid_api_key, sendgrid_webhook_signing_key, default_from_email, default_from_name
stripe_checkout81stripe_api_key, stripe_webhook_secret
amazon_s330aws_access_key_id, aws_secret_access_key, default_bucket
sms (Twilio)20twilio_account_sid, twilio_auth_token, default_from_number
ai13openai_api_key, anthropic_api_key
google_calendar6google_service_account_json
email_smtp5smtp_host, smtp_username, smtp_password
billing_paypal2paypal_client_id, paypal_client_secret
push_notification2fcm_service_account_json
billing_razorpay1razorpay_key_id, razorpay_key_secret, razorpay_webhook_secret
In a generated bundle the declaration is a plain list naming the exact keys, so whoever configures the project never has to guess whether a field is called api_key or stripe_api_key.
python
# config.py — shipped with the application bundle.
# These integrations arrive imported but inert. They need credentials
# per project, supplied after deploy, before any call to them succeeds.

INTEGRATIONS_NEEDING_CONFIG = [
    {"service_id": "stripe_checkout",
     "config_keys": ["stripe_api_key", "stripe_webhook_secret"]},
    {"service_id": "email",
     "config_keys": ["sendgrid_api_key", "sendgrid_webhook_signing_key",
                     "default_from_email", "default_from_name"]},
    {"service_id": "amazon_s3",
     "config_keys": ["aws_access_key_id", "aws_secret_access_key",
                     "default_bucket"]},
]
The credential goes to the keystore, a platform-admin surface. A read of the vault returns field names with values masked, so an operator can see that stripe_api_key is set without seeing what it is set to.
json
POST /api/v1/keystore/platform-secrets

{
  "service_id": "stripe_checkout",
  "secret_fields": {
    "stripe_api_key": "sk_live_...",
    "stripe_webhook_secret": "whsec_..."
  },
  "is_test": false,
  "label": "Acme production Stripe"
}

How It Works

  1. 1

    Import the service into the project

    POST /api/v1/services/import with a service_id, a domain and a project_uuid. Import creates a service_config record and registers the provider plugin in plugin_registry, so the platform knows the integration exists before any credential does.

  2. 2

    Supply the credential

    PUT /api/v1/services/{service_id}/config with a config_data object keyed by that integration's config_keys, or seed the keystore and apply it. The response carries config_status: active when every mandatory key is present, incomplete otherwise.

  3. 3

    Invoke an operation

    POST /api/v1/services/execute with the service_id, the operation name and an input object — the same endpoint every platform service uses. There is no per-provider API to learn and no per-provider client to ship.

  4. 4

    Receive the callback

    Providers report asynchronous outcomes by calling back. That is why the config_keys include sendgrid_webhook_signing_key, stripe_webhook_secret and razorpay_webhook_secret — the signing material that verifies an inbound callback really came from the provider.

A Stripe checkout illustrates the split. The application asks for a session and gets back a provider session id and a URL to send the buyer to. It never sees the secret key, never constructs a Stripe request, and never holds the key in a config file, an environment variable or an image.
json
POST /api/v1/services/execute

{
  "service_id": "stripe_checkout",
  "operation": "create_checkout_session",
  "domain": "acme",
  "project_uuid": "9f3c1b6a-2e5d-4f81-b0a7-3c5e9d1f2a44",
  "input": {
    "op": "create_checkout_session",
    "mode": "payment",
    "amount": 29.99,
    "currency": "usd",
    "product_name": "Annual membership",
    "quantity": 1,
    "client_reference_id": "order-7741",
    "success_url": "https://app.example.com/success?session_id={CHECKOUT_SESSION_ID}",
    "cancel_url": "https://app.example.com/cancel"
  }
}

// 200
{
  "success": true,
  "output": {
    "provider_session_id": "cs_test_a1b2c3...",
    "checkout_url": "https://checkout.stripe.com/c/pay/cs_test_a1b2c3...",
    "status": "created"
  }
}

ℹ️ Stripe is a real integration, not a stub

create_checkout_session, retrieve_checkout_session, create_payment_link and expire_session all reach the provider and return provider-shaped identifiers. The point of the design is not that the call is fake — it is that the key that authorises it never enters your application.

What You Get

  • •One invocation surface. Every integration is reached through POST /api/v1/services/execute with a service_id and an operation — the same endpoint that drives carts, approvals and workflows.
  • •Credentials that never enter your code. The vault listing surfaces field names with values masked, and the stored secret is sealed at rest, decrypted only on the authorised read path.
  • •Input validated before the provider is called: the execute wrapper checks it against the operation's input_schema, so a malformed call fails at the boundary rather than halfway through a payment.
  • •Responses filtered to the operation's output_schema — the shape you code against is the shape you get, and a provider adding a field does not change it.
  • •A configuration status you can act on: active or incomplete, per project, so an operator can see which integrations are actually usable.
  • •Per-project credentials. The same bundle runs against a sandbox key in one project and a live key in another, with no rebuild.
  • •Webhook signing material as a first-class config key on every integration that calls back, so verification is configuration rather than something you invent.
  • •Ten providers across email, payments, SMS, storage, calendar, push and AI keys — see Managed Integrations.

What You Decide

⚠️ A payment integration is not a billing system

Automatic recurring billing and dunning are not platform features. There is no renewal loop that charges a subscriber every month, no retry ladder for a declined card, and no free-trial mechanic. If your product renews, you define renewal yourself — as a workflow with a scheduled trigger that checks a billing date and calls the payment integration. Do not plan a subscription business around a loop the platform does not run.
The integrations give you a clean call and a safe place for the secret. They do not give you the logic on either side of that call, and that gap is where most of the real work lives.
  • •Reconciliation. A webhook tells you a checkout session was paid. What that means for your order record — which fields move, which transition fires, what happens if the callback arrives twice or before your own write lands — is entirely your logic.
  • •Idempotency and retry. If an execute call fails or times out, whether to retry, and how to make a retry safe when the first attempt may already have charged a card, is a per-operation decision.
  • •Provider-side setup. The account, the webhook URL registered in the provider dashboard, moving out of test mode, and any verification the provider demands are all outside the platform.
  • •The inert period. An application ships with its integrations imported but unconfigured, and calls fail with a clear error until credentials are supplied. Whether a user can reach that path before an operator configures the project is a launch-sequencing decision, not a bug.
  • •Which integration to use. email and email_smtp both send mail; billing_paypal, billing_razorpay and stripe_checkout all take money. The platform does not choose, and the choice is hard to reverse once records carry provider identifiers.