menu
Channels
Team

Webhooks?

July 27, 2020 at 7:24pm
I need to give a simple URL for a third-party service to post their JSON webhook to. How can I get this working in Booster?

July 28, 2020 at 9:43am
Hi thanks for the question.
Do you have control over the structure of the message that third-party service is posting?
If so, you can use the GraphQL API endpoint and structure the body of the Webhook message in the shape of a command/GraphQL mutation.
STRAIGHT TO THE POINT
> URL for the Webhook When you deploy your application using boost deploy, you will see an Outputs section where you can find the httpURL output. The URL you should use in the third party service web hook would be: <httpURL>/graphql
> Body of the webhook If you can define the template of the body of the webhook, then shape it as a GraphQL mutation. Assuming that the content of the message is in the template variable {{message}}, then the body would look like this:
{
"query": "mutation { HandleWebHookMessage(input: { rawMessage: \"{{message}}\" })"
}
> Command you need to define to handle the message
In your Booster application, define the following command:
@Command({
authorize: [ThirdPartyServiceRole], // Or any role you use for your third party service
})
export class HandleWebHookMessage {
public constructor(
readonly rawMessage: string,
} {}
public async handle(register: Register): Promise<void> {
const webhookMessage = JSON.parse(this.rawMessage)
// Work with the message
}
}
(Note: you can shape the command fields and webhook template in the way that's convenient to you. I assumed that the message was in a JSON string.)
MORE CONTEXT
Booster provisions a GraphQL API taking into account all the commands and read models you have defined in your app:
  • For each command, an equivalent GraphQL mutation operation is generated, with the same name and fields
  • For each read model, an equivalent GraphQL query operation is generated, with the same name and fields.
For example, if you have the following command:
@Command({
authorize: [ThirdPartyServiceRole],
})
export class HandleWebHookMessage {
public constructor(
readonly messageFieldOne: string,
readonly messageFieldTwo: number,
} {}
public async handle(register: Register): Promise<void> {
// Business logic to do with the message
}
}
Then you send that command with the following GraphQL mutation:
mutation {
HandleWebHookMessage(input: {
messageFieldOne: "This could be a raw JSON string"
messageFieldTwo: 42
})
}
You can even have a command with just one field that contains a raw JSON-encoded message. You would then decode it in the command handle method.
Edited
  • reply
  • like
Yeah, I don't have any control over the format of the message.. it's defined here: https://docs.mux.com/reference#webhook-events
  • reply
  • like
I can control where it POSTs to, but cannot control it's content or payload design.
  • reply
  • like
I see. In the current version, Booster only supports GraphQL "inputs", but it is already on the roadmap to allow defining inputs that can have any shape/format (even SOAP envelopes). I'll leave this question unresolved and will post any progress on this, especially when a new Booster version is released supporting it (it will be soon, before Booster hits the first production-ready version, 1.0.0)
  • reply
  • like

July 29, 2020 at 10:44am
One of the ideas we're currently discussing is to have some kind of adapter interface for commands, so you can define other input mechanisms in addition to GraphQL (which will be the default). I think that Booster should support common ones like HTTP+JSON and the way you enable them should be something like this:
@Command({
authorize: [Admin, ThirdPartyRole], // It would make sense to have special roles that represent third party apps access
adapters: [ // If adapters is not defined, the default is [ 'graphql' ]
'graphql', // Explicitly say that we'll use GraphQL. If we remove it from the list, it won't be available
'http+json', // Parse the request as HTTP with a JSON body, picking the token from the `Authorization: Bearer *****` header if needed. This should create a `/http/perform-high-five` endpoint
MyOwnHTTPAdapter, // We might also want to let users provide a class implementing their own ways to parse the HTTP request. Booster provides an interface with a set of methods to extract the URL pattern, the auth token, and the body from the raw request.
]
})
export class PerformHighFive {
public constructor(...) {}
public handle(register: Register) {
...
}
}
While this feature is cooked, the best way to overcome this limitation would be to create a lambda function separately that acts as a proxy, accepting the external HTTP request and sending the GraphQL mutation to the Booster's endpoint. You can create a lambda manually in the AWS console or use Serverless Framework to do it in a more CI/CD way. We would be happy to help you to figure this out if you find any issues.
Edited
  • reply
  • like
Yeah, an 'adapter' would make sense. I totally get that you should make it easy to do the normal thing with GraphQL, and having them all be the same data flow (aka, it ends up in GraphQL) makes a ton of sense.
like-fill
3
  • reply
  • like
private
This conversation has been locked