Skip to content
Create account or Sign in
The Stripe Docs logo
/
Ask AI
Create accountSign in
Get started
Payments
Revenue
Platforms and marketplaces
Money management
Developer resources
APIs & SDKsHelp
Overview
Versioning
Changelog
Upgrade your API version
Upgrade your SDK version
Essentials
SDKs
API
Testing
Stripe CLI
Tools
Stripe Dashboard
Stripe Projects
Workbench
Developers Dashboard
Stripe for Visual Studio Code
Terraform
Stripe Discord server
Features
Workflows
Batch jobs
Event destinations
Stripe health alertsStripe SignalsFile uploads
AI tools
Agent pluginsModel Context ProtocolAgent skillsStripe Directory
Extend Stripe
Overview
Build Stripe apps
Use apps from Stripe
Build extensions
    How extensions work
    Build a prorations extension with a script
    Build a custom action with a script
    Build a custom action with a remote function
    Extension points
    Scripts
      Define configuration and custom input
      Invoke endpoints from a script
      Handle errors in a script
    Custom actions
Custom objects
Security and privacy
Security
Activity logsStripebot web crawler
Privacy
Partners
Partner ecosystem
Partner certification
United States
English (United States)
  1. Home/
  2. Developer resources/
  3. Build extensions/
  4. Scripts
Private preview

Invoke endpoints from a scriptPrivate preview

Make HTTP requests to external endpoints from your scripts.

You can enable HTTP requests to an external endpoint from your script. Verify that your chosen extension point supports HTTP requests by checking the extension point specification page.

Add an endpoint to the app manifest

  1. Open the stripe-app.yaml file in your app’s root directory. The file includes an extensions key with an id that matches the extension ID you chose for the generate command. Inside that object, there’s a methods key.
  2. Add an endpoints key with the type custom_http at the same level as methods.
  3. Provide an id and the endpoint url.
extensions: - id: //your extension ID name: //your extension name methods: [] endpoints: - id: com.my_script.send_notifications type: custom_http managed_sandbox: url: https://your-url

To use the endpoint with live mode accounts, use live instead of managed_sandbox.

Call the endpoint from your script

In your source file, use endpointFetch to call the endpoint. Make sure the endpoint value matches the id you specified in the manifest. This example uses a custom workflow action, but endpointFetch works the same way in any extension point that supports it.

Mark your execute method as async and use await with endpointFetch to ensure the HTTP request completes before your script returns.

endpointFetch parameters

ParameterTypeRequiredDescription
endpointstringYesThe endpoint id declared in stripe-app.yaml.
pathstringYesURL path appended to the endpoint base URL.
method
  • 'GET'
  • 'POST'
  • 'PUT'
  • 'DELETE'
  • 'PATCH'
YesHTTP method for the request.
bodystringNoJSON-stringified request body.
headersRecord<string, string>NoAdditional HTTP headers to include in the request.

Response

On success, endpointFetch returns an object with the following properties:

PropertyTypeDescription
okbooleantrue for successful responses.
statusnumberHTTP status code (200-299).
body
  • string
  • undefined
Response body as a JSON string. Parse it with JSON.parse() to access the data.
export default class MyCustomAction implements Extend.Workflows.CustomAction<MyCustomActionConfig> { async execute( request: Extend.Workflows.CustomAction.ExecuteCustomActionRequest, _config: MyCustomActionConfig, _context: Context ) { const customInput = request.customInput as Record<string, unknown>; await endpointFetch({ endpoint: 'com.my_script.send_notifications', path: '/api/notifications', method: 'POST', body: JSON.stringify({ message: `Payment received from ${customInput.name}`, }), }); return {}; } getFormState( _request: Extend.Workflows.CustomAction.GetFormStateRequest, _config: MyCustomActionConfig, _context: Context ) { return { values: _request.values, config: {}, }; } }

The request object contains input specific to the extension point. In this custom workflow action example, request.customInput contains dynamic data mapped from the workflow trigger event, such as a customer name. You define these fields in custom_input.schema.json and reference them in stripe-app.yaml under the execute method.

The config object contains static values set once when the extension is configured, such as a notification channel. You define these fields in config.schema.json.

For more details, see Custom actions.

Runtime considerations

endpointFetch throws an error for non-2xx responses and network failures. Wrap calls in a try-catch block to handle errors. Scripts that invoke endpoints have a 30-second timeout.

Configure authorization

You can use token-based authorization or header-based authorization. First, create a secret on the account that runs the script. Then add an auth key to the app manifest stripe-app.yaml that specifies the authorization type and required values.

Configure token-based authorization

Provide the secret_name in the auth key.

endpoints: - id: com.my_cool_script.endpoint_token type: custom_http managed_sandbox: url: https://example.com/api auth: secret_name: endpoint_bearer_token type: bearer_token

Configure header-based authorization

Provide the header_name and secret_name in the auth key.

endpoints: - id: com.my_cool_script.endpoint_header type: custom_http managed_sandbox: url: https://example.com/api auth: secret_name: endpoint_header_secret type: header header_name: X-Foo-Header

Test endpoint calls

Script extensions generated from the Stripe CLI come with vitest set up. You should add unit tests to validate all aspects of your script’s desired behavior. In particular, use withEndpointFetchMock from the test helpers package to verify your endpointFetch calls without making real HTTP requests.

import { withEndpointFetchMock } from '@stripe/extensibility-test-helpers/endpoint-fetch';

The endpoint fetch test helper requires @stripe/extensibility-test-helpers version 1.3.0 or later. To upgrade, run this command from the root of your app:

Command Line
pnpm upgrade '@stripe/extensibility-test-helpers@^1.3.0'

Write a test

Declare an array of stubs, then run your extension code inside the withEndpointFetchMock callback. Each stub pairs a request pattern with a canned response you define.

it('sends a notification to the CRM', async () => { await withEndpointFetchMock( [ { request: { endpoint: 'com.my_script.send_notifications', method: 'POST', path: '/api/notifications', }, response: { status: 200, body: JSON.stringify({ delivered: true }), }, }, ], async () => { const result = await myExtension({ name: 'Jenny Rosen' }); expect(result).toEqual({}); } ); });

The mock matches each endpointFetch call against your stubs in declaration order, with the first match winning. After the callback returns, the mock fails the test if any mandatory stub was never called.

Match request fields

All request fields are optional. When you specify multiple fields, all must match (AND logic). Omitted fields match anything.

FieldMatches against
endpointThe endpoint id from stripe-app.yaml.
methodHTTP method (GET, POST, PUT, DELETE, PATCH).
pathURL path suffix.
bodyParametersValues of top-level fields in the JSON body, as strings. Nested and array-valued fields are compared in their JSON form, without spaces. Examples: string-value, 17, true, ["value1","value2"], {"key1":"value1"}.
headersRequest headers (case-insensitive keys).

Each field accepts a plain string (shorthand for exact equality) or a matcher object with one operator.

OperatorTypeDescription
equalTostringExact equality.
matchesstring or RegExpValue must match the pattern (unanchored).
containsstringValue must contain this substring.
doesNotMatchstring or RegExpValue must not match the pattern (unanchored).
absenttrueField must not be present.

Matchers equalTo, matches, contains, and doesNotMatch accept an optional caseInsensitive: true flag. If your script makes multiple endpointFetch calls, you select which response stub is used for each mocked call through request matchers. Even if your script makes only one call, narrowing the request pattern verifies your assumptions about that call, so the test doesn’t silently pass against the wrong request.

{ request: { endpoint: 'com.my_script.send_notifications', method: { equalTo: 'POST' }, path: { contains: '/api/notifications' }, bodyParameters: { channel: { matches: /^test_channel_[123]/, caseInsensitive: true }, messageLength: '1500', thread: { absent: true }, }, }, response: { status: 200, body: JSON.stringify({ delivered: true }), } }

Handle errors

The mock replicates production behavior. For non-2xx responses, withEndpointFetchMock throws a MockEndpointFetchError with the same shape as the production EndpointFetchError, so your error-handling code works without modification.

it('handles rate limiting', async () => { await withEndpointFetchMock( [ { request: { endpoint: 'com.my_script.send_notifications' }, response: { status: 429, body: JSON.stringify({ error: 'rate_limited' }) }, }, ], async () => { await expect(myExtension({ name: 'Jane Diaz' })).rejects.toThrow(); } ); });
StatusError code
400EXT_BAD_REQUEST
401EXT_UNAUTHORIZED
403EXT_NOT_ALLOWED
404EXT_NOT_FOUND
408, 504EXT_TIMEOUT
429EXT_RATE_LIMIT
503EXT_RESOURCE_UNAVAILABLE
OtherEXT_RUNTIME_ERROR

Mark stubs as optional

By default, every stub must be called during the test, or the test will fail. Set optional: true for calls that might not happen.

await withEndpointFetchMock( [ { request: { endpoint: 'primary-api' }, response: { status: 200, body: '{}' }, }, { request: { endpoint: 'analytics' }, response: { status: 200, body: '{}' }, optional: true, }, ], async () => { await myExtension(); } );

See also

  • Create an extension
  • Extension points
  • Store secrets
Was this page helpful?
YesNo
  • Need help? Contact Support.
  • Chat with Stripe developers on Discord.
  • Check out our changelog.
  • Questions? Contact Sales.
  • LLM? Read llms.txt.
  • Powered by Markdoc
On this page